目录
一、 程序的翻译环境和执行环境:
当写出一个 test.c 源文件的时候,如何通过编译链接产生一个可执行程序 test.exe 呢?
在本节课开始之前,我们是通过使用 ctrl+F5 来产生该可执行程序的,但是具体如何把一个源文件编译链接产生成一个可执行程序的过程,
将在本节课中进行阐述、
二、 详解编译+链接:
2.1 翻译环境:
在一个工程中,可能存在多个源文件,即 .c 文件,此时,工程中的每一个源文件都需要单独经过翻译环境进行处理,即,每一个源文件都
要单独经过编译器的处理,生成各个源文件对应的目标文件,即在windows系统下的 .obj 文件,当生成多个目标文件的时候,这些目标文
件一起再经过链接器,同时链接器在链接的时候,也会把链接库链接进去,即,链接器会把这些多个目标文件以及链接库连接在一起,最
后生成可执行程序、
当代码中使用到一些库函数的时候,就会把该库函数下的 .lib 文件,即静态库,这就是所谓的链接库,该链接库与多个目标文件,同时被链
接器连接在一起,生成最后的可执行程序,这些链接库中就包含了对应的库函数的信息、
这样不能观察到翻译环境中编译和链接过程中的细节,应该怎么做才可以观察到里面的细节呢?
2.1.1、如何在预处理结束后停止进行观察呢?
使用指令:gcc test.c -E
使用该指令后,屏幕上就会输出一堆东西,比较凌乱,此时就可以使用指令:gcc test.c -E > test.i ,就可以把 > 前面的指令所产生的所有
的结果输出重定向重新输出到一个其他的文件中去,此时,test.i 文件中就会保存 gcc test.c -E 指令所产生的所有的结果,然后再使用指令:
vim test.i 来打开该文件,就可以观察到其中的内容、
这一步就代表着完成了头文件的包含,头文件包含的具体过程就是把 stdio.h 中的内容拷贝了一份放在了该文件 test.i 中去了,所以,上图
中的红色框里面的内容以及上面省略掉的部分均是头文件 stdio.h 中的内容,被拷贝了一份放在该文件中、
如果更改源文件 test.c 内容为:
此时,通过指令 gcc test.c -E 得到预处理后的结果,然后再使用指令:gcc test.c -E > test.i 输出重定向到文件 test.i 中,然后再使用指令:
vim test.i 得到下面的结果:
所以,在预处理阶段主要完成了:
1、头文件的包含,即:#include<xxxxx>、
2、完成了#define定义的符号和宏的替换、
3、完成了注释的删除、
......
以上操作都是文本操作,在预处理之后的过程中,就看不到符号和宏了,已经被替换掉了,同时也看不到注释,因为已经被删除掉了、
2.1.2、如何在编译结束后停止进行观察呢?
使用指令:gcc test.i(test.c) -S 就可以生产 test.s 文件,该文件就是在编译过程停止后的到的文件、
该文件的内容为:
在预编译结束后,仍是C语言的代码,,而在编译结束后变成了汇编代码,也就意味着,该步骤将C语言代码替换成了汇编代码, 此转换过程主要包括:
1、语法分析、
2、词法分析、
3、语义分析、
4、符号汇总、
即把所有的源文件中的全局符号,包括函数名,全局变量等等的符号都汇总起来、
2.1.3、如何在汇编结束后停止进行观察呢?
使用指令:gcc test.s -c 生成 test.o (linux系统下)文件,该文件类似于 windows系统 下的 .obj 文件、
使用指令:vim test.o 打开该文件得到:
这不是文本文件,而是二进制文件,所以,在汇编过程中,将文本文件转化成为二进制文件,即,把汇编代码,转换成机器指令(二进制
指令) ,此时的二进制指令可由机器和一些工具来读懂,除此之外,该过程还生成了符号表、
其中,test.o 文件的格式为:elf格式,即文件的组成形式,是一种格式,每个目标文件,即 linux 系统下的 .o 文件,都会被分为一个个的
段,所有的 .o 文件划分的段是相同的,但是,不同的是,每个段中的内容是不相同的,此时,使用工具readelf就可以读懂二进制指令的内
容、
在VS编辑器下把加法的运算拆成两个文件来实现、
如上图所示,两个源文件,即 test.c 和 Add.c 文件都需要单独经过编译器处理:
此时,通过编译阶段就会进行符号汇总,把所有的符号汇总起来,再通过汇编阶段生成符号表,而符号表的使用是在链接阶段中使用、
如果把 Add.c 中的程序删除掉的话,那么经过编译,再经过汇编,链接,由于 Add.o 中没有符号,所以得到的符号表仅来源于 test.o 文
件,再经过合并与重定位符号表得到的仍是 test.o 文件中的符号表,但是由于该文件中的符号 Add 的地址是无效的,所以在运行程序的时
候,就会报错,即无法解析的外部符号,_Add、
2.2 编译过程的划分:
sum.c int g_val = 2016; void print(const char *str) { printf("%s\n", str); } test.c #include <stdio.h> int main() { extern void print(char *str); extern int g_val; printf("%d\n", g_val); print("hello bit.\n"); return 0; }
如何查看编译期间的每一步发生了什么呢?
1、 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在 test.i 文件中。
2.、编译 选项 gcc -S test.c 编译完成之后 就停下来, 结果保存在 test.s 中。3.、汇编 gcc -c test.c 汇编完成之后 就停下来, 结果保存在 test.o 中。
三、 执行环境(运行环境):
![](https://img-blog.csdnimg.cn/b144ca70e934435a83846d61019595db.png)
后期会陆续更新关于程序环境和预处理的知识,感谢观看,谢谢大家,记得点赞收藏哦~