宏(预编译)详解

宏(预编译)详解目录 一 程序的编译环境 二 运行环境 三 预编译详解 3 1 预定义符号 3 2 1 define 定义标识符 3 2 2 define 定义宏 3 2 3 define 替换规则 3 2 4 和 2 的作用 3 2 5 宏和函数的对比 3 2 6 宏的命名约定和 undef 指令 一

大家好,我是讯享网,很高兴认识大家。

目录

一、程序的编译环境

二、运行环境

三、预编译详解

3.1预定义符号

3.2.1 #define 定义标识符

3.2.2  #define 定义宏

3.2.3#define替换规则

 3.2.4 #和

        2)的作用:

3.2.5宏和函数的对比

3.2.6宏的命名约定和#undef指令

一、命名约定: 

二、#undef

 3.3条件编译

3.4文件包含

        1)本地文件包含:

        2)Linux环境的标准头文件的路径:

        3)库文件包含:


在学习预编译之前我们有必要先大致了解一下一个程序从开始到结束的过程,这样有利于我们加深对程序运行的理解。

一、程序的编译环境

        

在ANSI C的任意一种实现中,存在两个不同的环境。


二、运行环境


1.程序必须载入内存当中,再有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排
,也可能是通过可执行代码置入只读内存来完成。

2.程序执行便开始,随后调用main函数。

3.开始执行程序代码,这时程序员将使用一个运行时堆栈(Stack即函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储与静态内存的变量在程序的整个执行过程中一直保留他们的值。

4.终止程序,正常终止main函数,也肯能是意外终止。

a1b49f939cd548bb881bd584a654366a.png
讯享网

        如图所示,多个源文件(.c文件)单独经过编译器,进行编译生成目标文件(obj文件),这个过程为编译。多个目标文件与库函数中的链接库共同在链接器的作用下生成可执行程序(exe文件),这个过程为链接过程。

94e9b2e66c83482c99ab82951b9c3ddd.png

        如图所示,翻译环境 可以继续细分为编译和链接,编译还可以继续细分为预处理,编译,汇编,其中在翻译过程中首先进行的是预处理过程,在预处理过程中首先会把test.c源文件中的注释删除以及#include头文件包含和#define 符号的替换,在之后就会生成test.i文件为编译阶段做准备。

        到了编译阶段会进行对test.i文件的解读(包含 :语法分析,词法分析,语义分析,符号汇总)其中符号汇总为下阶段的符号表做准备,最后将test.i文件转化为汇编指令文件即test.s文件。

        接下来到了汇编阶段在linux环境下,test.s文件会被转化为存放二进制test.o的目标文件文件(在win下转化为test.obj文件),这些二进制文件是以elf(linux环境下)文件格式存放的,elf文件又把二进制文件分为不同的数据段,最后在把前面编译的符号的汇总整理成符号表

        编译阶段结束,接下来就是链接阶段了,链接阶段首先把不同文件的相同段进行合并,形成新的数据段表,其次在对不同文件的的相同符号进行合并,合并为新的符号表,值得注意的是在形成符号表的过程总中有些单独文件的虚拟地址会被分配有效地址(重定位)加入新的符号表。

        以上就是程序从开始到结束的大致过程了,如果想了解更多的编译链接过程可以参考《程序员的自我修养》


 

三、预编译详解

3.1预定义符号

 __FILE__    //进行编译的源文件
__LINE__    //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

我们不妨打印出来这些预定义符号

#include<stdio.h> int main() { printf("%s\n",__FILE__); printf("%d\n",__LINE__); printf("%s\n",__DATE__); printf("%s\n",__TIME__); printf("%d\n",__STDC__); return 0; }

讯享网

c1406723ab204981b62f75ce933b47d1.png        可以发现,打印出来的结果跟预期一样,由(__STDC__)的结果看,dev C++遵循ANSIC。


 

3.2.1 #define 定义标识符

         用法:#define name stuff

在有了#define预处理命令后我们可以进一步对上面的预定义符号进行更加方便的表示,在main函数外使用#define+名字+要替换的内容,就可以在全局范围内使用这个宏,例如下面的代码:

 

讯享网#include<stdio.h> #define DEBUG_PRINT printf("file:%s\tline:%d\tdata:%s\t \ time:%s\n",__FILE__, __LINE__, \ __DATE__, __TIME__) /*换行加'\'(转义字符,转义了回车)为了消除define的影响*/ int main() { DEBUG_PRINT; return 0; }

        值得注意的是在C语言中,#define预处理指令使用了printf函数只能处理单行内容,如果想换行必须在每一行的末尾加上'\'转义字符才能把换行表示成字符来处理,否则会报错。 

代码执行结果如下:

fb0fb2a0f9664fb88fbfaacaa25368a1.png

         注意:在#define后面最好是不要加上分号,因为这样可能会造成歧义。


3.2.2  #define 定义宏

        #define 机制包括了了一个规定,允许把参数替换到文本当中,这种实现通常称为宏(macro) 或者定义宏(define macro)

        宏的申明方式:#define name(parament-list) stuff , 其中parament-list是一个由逗号隔开的符号表,他们可能出现在stuff中。
        注意:1.参数列表的左括号必须与name紧邻。2.如果两者之间有任何空白的存在,参数列表就会被解释为stuff中的一部分

看看下面的例子:

#define Add(x,y) (x+y) int main() { int a = 3; int b = 7; int c = Add(a,b); printf(“%d”,c); return 0; }

    035fbf95791b455e80c41b87bf53b6ea.png


 

3.2.3#define替换规则

在程序中扩展#define定义符号和宏时, 需要涉及这几个步骤:

        1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是,他们首先被替换。
        2.替换文本随后**入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
        3.最后,再次对结果文件进行扫描,看着他是否包含任何由#define 定义的符号,如果是就重复上述处理过程。

注意:
        1.宏参数和#define定义中可以出现其他的#define定义符号,但是对于宏,不能出现递归。
        2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。


 

 3.2.4 #和

        1)#的作用:

思考这样一个问题:如何把参数插入到字符串当中呢?

讯享网#include<stdio.h> int main() {     int a = 10;     printf("The value a is %d\n",a);          int b = 20;     printf("The value b is %d\n",b);     return 0;  }  


        例如:我想要The value a is ...  The value b is...  The value c is...这样类似的输出如果用printf函数,少量的字符串CV一下就行,但是
如果需要特别多行类似的语句printf函数是做不到的。那么宏做不做得到呢?其实宏有种方法是可以做到的,就是符号'#'。

#include<stdio.h> #define PRINT(n) printf("The value "#n" is %d\n",n) int main() {     int a = 10;     PRINT(a);          int b = 20;     PRINT(b);     return 0;  } 


        把一个字符串从要替换的字符串的中点分成两个字符串,除了想要替换的字符串以外,另外两个字符串都需要完整的"",在要替换的文本前加上#,这样就可以轻松替换了。
实质上这个宏其实是PRINT(n) printf("The value ""n"" is %d\n",n),相当于在'#'后面部分的字符串改变后又被重新拼接起来形成一个新的完整的字符串。

        我们来思考另一个问题:如果两个参数的类型不一样,如何能用一条语句实现呢,比如,我想要一个a为int 型,b 为float型,这样看来printf函数还是不能实现,难道宏还可以吗,没错,宏就是能一劳永逸!我们来看下面代码:

讯享网#include<stdio.h> #define PRINT(n,format) printf("The value "#n" is " format "\n",n) int main() {     int a = 10;     PRINT(a,"%d");          float b = 10.5f;     PRINT(b,"%f");     return 0; } 


        在前面代码的基础上,加上了format类型格式,把输出控制符(%d,%f...)用format代替,且format需要单独的一个双引号,这样在传参的时候只需要传数据类型和输出控制符就可以实现把不同的输出控制符插入到字符串当中,怎么样,是不是很方便呢?


        2)的作用:

可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 

这句话是什么意思呢?我们先来看一下下面的代码:

#include<stdio.h> #define CRT(x,y) xy int main() { int DataSum = 100; printf("%d\n", CRT(Data, Sum)); return 0; }

结果为:a8fdf81c55eb414ab86e28151ef72f03.png发现了打印的值和DataSum的值相同,这也就说明了这个宏能将两个片段合并成一个片段,这就是的作用了。 


 

3.2.5宏和函数的对比

        宏通常被应用于执行简单的运算,就像计算两个数的加法:

讯享网#include<stdio.h> #define Add(x,y) (x + y); int Add_Fun(int x, int y) { return x + y; } int main() { int x = 3, y = 2; printf("%d\n", Add_Fun(x, y)); int c = Add(x, y); printf("%d",c); return 0; }

为什么不用函数来完成这个任务呢?

原因有两点

        1.用于调用函数和函数返回的代码可能比实际执行这个小型计算机工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。

        2.更为重要的是,函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用,反之这个宏可以适用于整形长整型浮点型等可以用于>来比较的类型。宏与类型无关。 

宏的缺点:

        1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则大幅度增加程序长度。

        2.宏是没办法调试的。

        3.宏由于类型无关,也就不够严谨。

        4.宏有时候会带来运算符优先级问题,导致程序发生错误。

所以根据不同的情况进行选择使用宏还是函数有各自的优势。

        宏和函数的对比:

       

        性

                #define定义宏

         函数     

        代

        码

        长

        度

每次使用时,宏代码都会**入到程序中。除了非常小的宏之外,程序长度会大幅组增长。 函数代码只出现于一个地方;每
小讯
上一篇 2025-03-17 15:43
下一篇 2025-03-27 15:53

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/55055.html