1、编译的过程
源码.c->(预处理)->预处理过的.i源文件->(汇编)->汇编文件.S->(编译)->目标文件.o->(链接)->elf可执行程序
预处理:预处理器
汇编 :汇编器
编译 : 编译器
链接 : 链接器
预处理就是调用预处理器做一些代码的前期执行动作,使得编译器可以专心只做和编译相关的事情。
2、常见的预处理
这里介绍常见的预处理的方式,一般为四种。
gcc 中提供了只进行预处理而不进行编译的方法:
gcc -E xx.c -o xx.i
这样就可以从 .i 文件中去学习,去认识预处理。
(1)头文件的包含:#include <> 和 #include""
#include <> : 是在系统或者编译器默认的路径去寻找头文件
#include"" : 在当前的工程目录下去寻找头文件,
包含头文件的真实含义:
其实就是将 include 包含的头文件,将这个头文件里面的内容在原地进行展开,替换 include 这一行。这些工作,就是由预处理器进行处理。
(2)注释
自己学习的时候编写的注释,只是给自己查看的,而不是给编译器,所以就在预处理的阶段拿掉所有的的注释。预处理器完成清除所有的注释之后,才交给编译器去编译。
(3)条件编译:#if #elif #endif #ifdef
一般的情况下,源代码都是参加编译的,但是又希望在满足一定的条件下才进行编译,也就是一部分的内容指定编译的条件。
例子1:
例子1: #if 条件 // code #else // code #endif 例子2 #ifdef 条件 // code #else // code #endif
#define DEBUG int main(int argc, char *argv[]) { #ifdef DEBUG printf("DEBUGGING "); #else printf("NOT BUGGING "); #endif // DEBUG printf("OK "); while (1); } 打印输出: DEBUGGING OK
当# ifdef 条件成立的时候,就会执行下面的代码动过;当没有定义,也就是条件不成立则执行 else 的部分。
(4)宏定义
宏定义代表了一些特定内容的标识符,在预处理的阶段预处理器会将宏定义直接展开为定义的内容。完成的当作,其实就是原封不动展开(做的是文本替换工作)。
习惯上总是全部用大写的字母来定义宏。
带参:
#define MAX(a,b) ( ((a)>(b)) ? (a) : (b) )
宏定义传参了,传参的部分都加上括号,最后再添加添加括号。不然会带来宏定义的二义性。
不带参数:
定义一年的秒数:
#define SEC_YEAR (365*24*60*60UL)
注意:(1)当出现数字的时候,默认的是 int 类型的,
(2)一年的描述超过了 int 类型的范围,会造成数值的溢出。所以,就需要将数值转为无符号整数。记住需要将 UL 放到括号的内部。
宏定义实现条件编译:
#define DEBUG #ifdef DEBUG #define debug(format,args) printf(format,args); #else #define debug(fromat,args) #endif // DEBUG int main(int argc, char *argv[]) { int a = 1; debug("a = %d ",a); while (1); }
当定义了 DEBUG 的时候,debug 可以进行打印参数,而当没有定义的是则为空。
(5)特殊宏定义
编译器提供了一些特殊的宏定义,在预编译的阶段将特殊符号进行特换。
__LINE__ : 包含当前的行号
__FILE__: 包含当前的文件名
__DATE__: 包含当前的日期
__TIME__: 包含当前的的时间
__FUNC__ : 包含当前的函数
printf("%s,%s ",__LINE__,__FUNC__);