目录
前言
本文分享一波C语言中有关程序环境和预处理相关的知识,由于笔者水平有限,难免存在纰漏,读者各取所需即可。
1.程序的翻译环境和运行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
第一种是翻译环境,将源代码转换成可执行的机器指令。
第二种是执行环境,用于实际执行代码。
(1)翻译环境
对于翻译环境,主要有编译和链接两个步骤。
源文件编译生成目标文件,目标文件再经由链接器与链接库链接最终生成可执行程序。
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
实际上,编译过程可分为三个过程:预编译、编译和汇编。
对于编译过程中的符号汇总,举个例子,下面是同一工程下的两个源文件,那么在编译过程中,所谓汇总的符号,对add.c来说就是Add,对test.c来说,就是Add和main。实际上汇总的符号是文件中的全局符号(全局变量、函数名)。
而后在汇编过程中,汇总的符号会生成elf格式的符号表,是分段的,装着符号和其对应地址,当进入链接过程时,符号表合并,相同符号会合并剩下一个有效符号。比如说上面的例子中,test.c文件中的Add符号不是有效符号,其地址无实际意义,因为该函数的定义是在Add.c文件而非test.c文件,test.c中仅有其声明罢了,实际上在链接时两个Add符号合并后保留Add.c文件中的Add的地址。
例题
由多个源文件组成的C程序,经过编辑、预处理、编译、链接等阶段会生成最终的可执行程序。哪个阶段可以发现被调用的函数未定义?
答:链接阶段。
分析
预处理只会处理#开头的语句,编译阶段只校验语法,链接时才会去找实体,所以是链接时出错的。
这里附上每个步骤的具体操作方式:
预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。
链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。
需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。
(2)运行环境
在运行环境中进行程序的执行,其过程:
1. 程序必须载入内存中。
在有操作系统的环境中:一般这个由操作系统完成。
在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
2.预处理详解
2.1 预定义符号
__FILE__ __LINE__ __DATE__ __TIME__ __STDC__ |
//进行编译的源文件 //文件当前的行号 //文件被编译的日期 //文件被编译的时间 //如果编译器遵循ANSI C,其值为1,否则未定义 |
这些预定义符号都是语言内置的。
例如:
printf("file:%s line:%d \ndate:%s time:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);
那这么些符号有啥用呢?可以用来记录日志。
2.2 逻辑行
在预处理前,编译器定位到每一个续行符即反斜杠 \ ,删除它并且将几个物理行合成一个逻辑行。如:
printf("That's \
right."); //两个物理行
printf("That's right"); //转换成一个逻辑行
编译器用一个空格字符代替每一条注释,所以实际上注释压根就没进入到编译过程,更不用说最后的可执行程序了,因为注释是给人看的,机器并不会去看。
预处理器指令从#开始,执行长度为一个逻辑行,所以预处理指令若要使用多个物理行的话需要用续行符合成一个逻辑行。
2.3 #define定义宏
每行#define由三部分组成,用宏代表值的称为类对象宏,代表函数的称为