程序的编译
写了这么多程序,那么程序到底是如何编译的呢?每一个源文件都会经过编译与链接的过程,最终才会展示出来。过程大致如下:源文件先经过编译器编译为目标文件,最后由链接器将库文件和目标文件结合,成为可执行文件。接下来,我们来探讨一下编译与链接的具体过程。目录一、编译1.1 预编译1.2 编译1.3 汇编二、链接三、运行一、编译编译可以分为三个阶段:预编译、编译、汇编。为了方便演示,这里使用Linux环境演示
写了这么多程序,那么程序到底是如何编译的呢?
每一个源文件都会经过编译与链接的过程,最终才会展示出来。
过程大致如下:
源文件先经过编译器编译为目标文件,最后由链接器将库文件和目标文件结合,成为可执行文件。
接下来,我们来探讨一下编译与链接的具体过程。
目录
一、编译
编译可以分为三个阶段:预编译、编译、汇编。
为了方便演示,这里使用Linux环境演示。
1.1 预编译
这个过程主要由三个步骤:
1. 将头文件的声明导入源文件中
2. #define 的替换
3. 删除注释
这些都属于文本操作。
在Linux 环境下,对应代码为: gcc -E test.c -o test.i
这里写一串代码示例:
#include <stdio.h>
#define MAX 10
//This is a note.
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
printf("%d\n", Add(a, b));
printf("%d\n", MAX);
return 0;
}
输入命令,会生成 .i 文件,即预编译好的文件,再打开 .i 文件,如下:
1.2 编译
这个过程主要将C语言代码转为汇编代码,主要是四个过程:
1. 语法分析
2. 词法分析
3. 语义分析
4. 符号汇总
在Linux 环境下,对应代码为: gcc -S test.c
还是以上面那串带码为例,编译后的结果是 .s 文件,打开它如下:
一堆看不懂的代码哈哈,其实它们就是古老的汇编语言。
转换为汇编的四个过程中涉及到编译原理,这里不多阐述。
其中的符号汇总需要讲一下,就是将全局变量以及函数形成一个符号。
1.3 汇编
这个步骤主要将汇编代码转为二进制文件,
这个过程会生成符号表。
刚刚讲到,每个全局变量与函数会形成一个符号,
这个过程就是将每一个符号结合生成一个符号表。
一个符号是一个标识,此过程还会给符号一个地址,最终将所有符号及其地址组合,形成符号表。
对应 Linux 的代码是:gcc -c test.c
会生成 .o 文件,打开文件:
这个相信没人能看懂了,这是只有机器能看懂的二进制文件。
二、链接
这个过程主要干两件事:
1. 合并段表
2. 符号表的合并和重定位
那么段表是什么意思呢?
一个汇编后的 .o 文件都有一个段,
比如我们将一个函数写入 Add.c 的文件中,在 test.c 中调用。
那么这两个文件都会经过编译,最后有一个自己的段。
比如这个test.c调用了Add.c文件,所以这两个段会结合在一起,即.o文件的结合,最终形成段表。
那么符号表的合并是什么意思呢?
之前我们讲到全局变量与函数都会生成符号,在汇编阶段生成符号表,
在main函数中调用时,其实也会有这样一个符号表,
但是main函数内的只能算一个标识,没有自己的意义,
靠着这个标识去找到main函数外的函数或者全局变量。
一旦找到,就是将他们的符号表合并。
三、运行
最后我们来看看运行。
程序执行的过程:
1. 程序先载入内存中。一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程并一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
感谢你能看到这,如有帮助,希望点个赞👍。
更多推荐
所有评论(0)