目录
1.C语言的翻译环境和执行环境
在ANSI C(美国国家标准协会(ANSI)设定的C语言标准)中存在两种环境
1.翻译环境:在这个环境中源代码被转换为可执行的机器指令
2.执行环境:实际应用于代码的实现
1.1 C语言的翻译环境
把源代码转换成可执行的机器指令需要经过编译与链接,而编译又细分为预处理(或者说预编译),编译,汇编。如图所示:
预编译:在gcc环境下以执行-E命令,test.c-->test.i 而test.i就是我们预处理之后的文件了。下面由具体观看.i文件的操作,预处理主要做的是文本操作,1.头文件的包含(#include),把头文件从库中引出放到文件中。2.把注释的文本去除。3.把#define定义的符号进行替换。4.条件编译。(#if,#endif,#elif等是在条件编译期间选择的)
编译:在gcc环境下-S命令,test.i-->test.s 产生编译后的test.s文件。编译主要作用:把c语言代码转换成汇编代码,其中涉及语法分析、词法分析、语义分析以及符号汇总。
汇编:在gcc环境下-c命令,把汇编文件.s转换为目标文件test.o(VS环境后缀.obj),它的作用是把汇编代码翻译成为二进制指令的机器语言,同时把文件中的函数标识等结合其所分配的符号地址形成符号表。(不同文件有各自的符号表)
链接:待编译完成后与进行链接,把该项目的所有文件链接,其主要分为1.合并段表(各个目标文件中被分为不同片段,不同文件的同一个段区合并在一起形成可执行程序中的各个段)。2.符号表的合并和重定位(每个目标文件.obj有各自的符号表,他们之中有相同或者不同的函数标识,相同名称的标识由于是不同文件产生故有不同的符号地址,于是统一重定位(声明只是个承诺,链接期间分配确切地址)形成最终的符号表,所以在不同文件调用函数都是从同一个地址出发的)(链接函数的定义,链接库里面的函数都是在这一层完成的)
举个例子:
test.c函数标识Add的符号表匹配的地址,假设为0x0012ff40
int Add(int x, int y) { return x + y; //test0.c文件中的 }
test1.c文件中有三个函数标识分别为printf,main,Add函数,该文件符号表匹配的Add地址为0x0025ff20 。于是最后链接完成得到可执行文件后两个Add就会重定义为同一个地址(以定义所在地址为主),以至于可以通过extern外部声明找到test0.c中的Add函数。但是有声明的
地址,不代表有这个函数的定义,最后会在链接中判断是否存在函数定义。
extern int Add(int x, int y); int main() { int a = 10; int b = 20; printf("%d\n", Add(a, b)); //test1.c文件中 return 0; }
值得一提的是:汇编阶段形成的符号表包含着函数标识及符号地址,这个地址并没有实际分配到空间,内存地址实际空间分配是在链接期间进行的。编译期间的符号地址是为链接器提供条件,方便寻找到该位置。具体符号地址取决于编译器,可能为偏移量或其他编码供其自己寻找。
内容参考于下面两文章,仅作为学习参考。有问题可以评论区共同交流
1.11 .i文件的生成
vs中属性—— C/C++ —— 预处理器 —— 预处理到文件(改为是)
再按仅编译(ctrl+F7)即可在debug文件中看到.i文件
里面最后几行就是预处理后的程序代码
1.2 C语言的执行环境
程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。