程序的编译和链接
- 1、翻译环境和运行环境
- 2、翻译环境
- 3、预处理(预编译)
-
- 3.1编译
- 3.2汇编
- 3.3链接
- 3.4GCC编译过程分解
- 4、运行环境
1、翻译环境和运行环境
在ANSLC的任何一种实现中,存在两个不同的环境。
- 翻译环境:源代码--->可执行的机器指令(二进制指令)
- 执行环境:实际执行代码
这就是当我们写代码:
C
#include<stdio.h>
int main()
{
printf("hehe\n");
return 0;
}
屏幕上会打印hehe的原理
2、翻译环境
翻译环境由编译 和链接 两大过程组成,而编译 又可以分解成:预处理、编译、汇编三大过程。
一个C语言的项目可以由多个.C
文件组成,下面是组成的原理
-
多个.C文件单独经过编译器,编译处理生成对应的目标文件
-
Windows环境下的目标文件的后缀是
.obj
,Linux环境下目标文件的后缀是.o
-
-
多个目标文件和链接库一起经过链接器处理生成最终的可执行程序
-
链接库是指运行时库或第三方库
把编译器再展开3个过程,那就变成了下面图:
执行顺序:C代码 --- 预处理 --- 编译 --- 汇编 --- 链接 --- 可执行程序
3、预处理(预编译)
在预处理阶段,源文件和头文件会被处理成为.i
为后缀的文件
在gcc
环境下想观察对test.c
文件预处理后的.i
文件,命令为:
gxx -E test.c -o test.i
预处理阶段主要处理那些源文件中#开始的预编译指令。比如:#include,#define,处理的规则如下:
- 将所有的
#include
预编译指令,如:#if、#ifdef、#dlif、#else、#endif
- 处理#include预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程时递归进行的,也就是说被包含的头文件也可能包含其他文件
- 删除所有的注释
- 添加行号和文件名标识,方便后续编译器生成调试信息等
- 保留所有的#pragma的编译器指令,编译器后续会使用
经过预处理后的.i
文件中不再包含宏定义,因为宏已经被展开。并且包含的头文件都被插入到.i
文件中。所以当我们无法知道宏定义或者头文件是否包含正确的时候,可以查看预处理后的.i
文件来确认。
3.1编译
编译过程就是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析及优化 ,生成相应的汇编代码文件
编译过程的命令:
gcc -S test.i -o test.s
如果对下面代码
array[index] = (index + 4)*(2 + 6)
编译时,编译器会怎么做呢?
根据记号,会生成下列的语法树
3.2汇编
汇编器 时将汇编代码转变成技巧可执行的指令
汇编命令如下:
gcc -c test.s -o test.o
3.3链接
当有两个甚至多个.c文件时,编译器是先把每个函数的链接合并到一起,然后再使用,如下图中的加法函数:
![[QQ20251019-111103.png]]
3.4GCC编译过程分解
// 分布执行,观察每个阶段的输出
gcc -E test.c //只预处理
gcc -S test.c //生成汇编代码
gcc -c test.c //生成目标文件
gcc test.o //链接生成可执行文件
4、运行环境
- 程序必须载入内存中。在有操作系统的环境中:⼀般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程⼀直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。