目录
C语言程序的编译+链接
在ANIS C的任何一种实现中,都存在两个不同的环境:翻译环境和执行环境。在翻译环境中,源代码被转换成可执行的机器指令;执行环境用于实际执行的代码。
在翻译环境中,组成一个程序的每个源文件通过编译过程分别转换成目标代码,然后每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序,链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的的程序库,将其需要的函数也链接到程序中。
编译本身分为三个阶段:预处理、编译、汇编
在预处理阶段,对源文件中的预处理指令进行处理,包括头文件的包含,#define定义符号进行替换以及注释的删除,这些操作都是文本操作。
在编译阶段,编译器会对源文件进行语法分析、词法分析、语义分析、符号汇总,符号汇总包括函数和全局变量的汇总等。
在汇编阶段,生成可重复定位的目标文件,其中包括形成了符号表。
在链接阶段,合并段表以及符号表。
在程序执行的过程中,程序必须载入内存中。在有操作系统的环境中,一般由操作系统完成,在独立环境中,程序的载入必须由手工完成,也可能是通过可执行代码置入只读内存来完成。程序的执行是从调用main函数开始的,程序代码开始执行,这个时候程序将使用一个运行堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的比变量在程序的整个执行过程中一直保留他们的值。
预处理详解
预定义符号介绍
__FILE__//进行编译的源文件
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间
__STDC__//如果编译器遵循ANSI C 其值为1,否则未定义
预处理指令#define
#define定义标识符
语法:
#define name stuff
举个例子:
#define MAX 10000
#define reg register //为register这个关键字创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把break加上
//如果定义的stuff过长,可以分为几行写,除了最后一行外,每行的后面都加一个反斜杠\(续行符)
#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或者定义宏(define macro)
宏的声明方式:
#define name(parament-list) stuff
其中parament-list是一个逗号隔开的符号表,它们可能出现在stuff中
注:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
警告:宏存在一个问题:
#define SQUARE(x)x*x
int main()
{
int a=5;
printf("%d\n",SQUARE(a+1));
return 0;
}
这段代码在替换文本的时候,参数x被替换成a+1,所以就变成了
printf(“%d\n",a+1*a+1);
所以对于数值表达式进行求值的宏定义都应该适当使用括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。于是我们修改为:
#define SQUARE(x)(x)*(x)
这样,就可以打印出我们预料的36
#define的替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替换
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程
注意:
- 宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归;
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
那么如何把参数插入到字符串中?
只有当字符串作为宏的参数的时候才可以把在字符串放在字符串中,同时使用#,把一个宏参数变成对应的字符串。
比如:
int i=10;
#define PRINT(FORMAT,VALUE) \
printf("the value of "#VALUE" is "FORMAT"\n",VALUE);
...
PRINT("%d",i+3);
最终输出的结果为:
the value of i + 3 is 13
#undef
这条指令用于移除一个宏定义