C语言的编译过程主要分成3个阶段:
1:预处理
2:编译
3:链接
1 预处理
该阶段有两个工作,一是将#define 给扩展开来,二是通过#include将.h文件加入到该代码中。
代码中所有用到define一样字符串的位置通过define的规则给一一替换。前一个#define 会影响后一个#define,但是代码中常量字符串不会被替换。
例子:
#define I 6
#define J I*5 //I会被替换成6
int main()
{
int i=J; //J会被替换成6*5
char a[30]="dgJ"; //J不会被替换
return 0
}
#define 的一些高级运用:
#define MAX(x,y) ((x)>(y)? (x):(y))
对于MAX(5,6)将被替换为((5)>(6)? (5):(6))
但是对于MAX(f(5),f(6))将被替换为:
((f(5))>(f(6))? (f(5)):(f(6)))
函数被多调用,如果函数本身很复杂则得不偿失。
所以可以写成
#define MAX(x,y) ({typeof(x) _x=x;\
typeof(y) _y=y;\
_x>_y? _x:_y;})
一个块的值(即大括号{})是最后一个表达式的值。
#和##在宏定义中的作用
#define A(a,b) a##b //连接a和b
#define B(r) #r //将r变为字符串”r”
#include的具体处理
#include的头文件会被依次加入代码中。
如果有递归定义的#include,以前的实现中将无限递归加载,现在的大多实现中解决了这个问题,但是还是最好自己用#ifndef __xxx__的方式来限定一个.h只能被调一次。
#ifndef __XXXXH__
#define __XXXXH__
...
...
...
#endif
2 编译和链接
具体做了什么,自己百度。在此只是说一个很有意思的现象。在链接阶段,即使你在代码头部没有包含标准库的头文件,但是代码中调用了标准库函数,实际链接的过程中,会准确定位到该函数,程序将正确执行。
看例子就懂了:
#include <stdio.h>
int strlen(const char *,int);
int main()
{
int i;
i=strlen("sdf",5);
printf("%d\n",i);
return 0;
}
如果没有加int strlen(const char *,int);会报strlen没有声明的错误,所以要加上。
实际上,我没有实现strlen函数,也没有加string.h的头文件,但是程序运行正常(将会调用标准库的strlen())。因为编译阶段,只是简单的一个语法检查,它认为strlen(“sdf”,5)的调用没有问题,因为我在前面声明了这个函数。链接阶段,会搜整个标准库一遍,所以strlen将调用标准库string.h中的strlen()函数。
真实执行中,参数5入栈、参数char *入栈,然后调用标准库string.h里的strlen(const char *)。但是标准库的strlen()明明只有一个参数,会出现执行错误么?答案是不会,因为char *无论是哪种相对栈指针的偏移是一致的,strlen()函数将能正确访问到char *变量,int参数5虽然入栈,但是函数中没有访问它。这一块具体可以看C语言内存管理中的栈区加深理解。