1.程序的翻译环境和执行环境
在ANSI C的任何一种实现中,都存在两个不同的环境。
- 翻译环境,在这个环境中源代码被转换成可执行的机器指令。
(例如:vs2022这个集成开发环境就包括翻译环境)- 执行环境, 它用于实际执行代码
2. 详解编译+ 链接
一个.c文件想要编程一个可执行程序,需要经过编译和链接两步, 而编译又可细分为预编译,编译和汇编三过程,如图:
2.1 翻译环境
一个项目可能是由很多个.c文件共同构成的,每个.c文件需要分别进行编译产生.obj
后缀的目标文件(windows环境)
在linux环境下是
.o
为后缀
接着在链接器的作用下将所有目标文件结合在一起进行最后产生了可执行程序。
- 组成一个程序的每个源文件单独通过编译过程分别转换成目标代码(object code)
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时会引入标准C函数库中任何被程序引用到的函数,而且它可以搜索程序员个人的程序库,将其所需要的函数也链接到程序中。
2.2 编译的三部分
下面,我们就以一个例子来为大家简单的解释一下每个步骤都在干什么。
注:以下例子只是用来演示c语言编译各个阶段的特点,无实际意义
test.c
#include<stdio.h>
#include "add.c"
//这是注释
#define M 100
extern int g_val;
extern int Add(int x, int y);
int main()
{
//开始
struct s k;
int b = 10;
int c = Add(M,b);
printf("%d", c);
return 0;
//结束
}
add.c
int Add(int x, int y)
{
return x + y;
}
struct s
{
int a;
char c;
};
int g_val = 100;
预编译
在预编译过程中,会进行的操作有#include
头文件的包含以及#define
宏的替换(预处理指令)还有注释的删除,总的来说,就是对代码文本进行改进。
我们用gcc编译器来编译代码,通过指令使其停在预处理结束之后的阶段,由此来观看代码文本的变化。
在终端输入: gcc -E test.c -o test.i
即可生成预处理之后的文件,然后我们看看文件中的内容,如图:
编译
编译过程主要将进行语法分析,词法分析,语义分析以及符号汇总四个内容,同样,我们可以通过命令的方式得到编译后的文件。
这里简单讲解一下符号汇总,其实就是把函数名和全局变量之类的汇总起来,便于下一步作用。
命令:gcc -S test.c
,然后我们看一下文件内容。
我们可以看到,其实经过编译之后,就是把我们的c语言代码转换为汇编代码。
汇编
接下来就是编译的最后一步,汇编了,通过这一步之后,我们写的c程序就完全转换成了目标文件(object code),由于目标文件是由二进制指令构成,所以我们是没办法看懂的,但是依旧可以通过指令的方式得到汇编后生成的文件。
指令: gcc -C test.c
通过这一指令,将生成test.o的文件,其和我们用vs编译生成的.obj 后缀文件性质相同。
汇编过程的进行大致有以下两步:
- 形成符号表
在编译过程中进行了符号汇总,而形成符号表简单来说就是将这些符号与地址配对,符号表 = 符号+地址
例如上面那个例子,两个文件的符号表就可以如此表示:
(地址是博主随便设的(doge))
test.c的符号表
符号 地址 g_val 0x010 Add 0x783 main 0x666 add.c的符号表
符号 地址 Add 0x200 g_val 0x300
- 汇编指令-> 二进制指令 ------->test.o和add.o
2.3链接
链接部分主要执行两个操作:
- 合并段表
- 符号表的合并和符号表的重定位
这里为了解释合并段表,我们进行一定的扩展:
在linux环境下,生成的test.o和可执行程序都是
elf
格式的文件,而elf格式的文件时分段式的结构,每个段放置不同功能的数据,如图:
也就是说,这里段表的合并就是把多个.o文件中相同功能的段合并在一起。
而符号表的合并和重定位就是将相同的符号合并,并且替换掉无效的地址。
就拿刚才的例子来说,test.c 中的g_val 和Add符号是用extern
外部引入的,需要在其他的文件才能找到其定义,也就是说,此时test 文件的符号表中这两个的地址是无效的,真正有效的地址在add.c中,因此需要进行合并。
上诉代码合并之后的符号表如下:
符号 | 地址 |
---|---|
g_val | 0x300 |
Add | 0x200 |
main | 0x666 |
以上就是从.c文件到.exe 文件的大体过程啦,当然其实编译的过程还有很多细节,如果有机会,之后博主会继续补充的嘿嘿!
3.运行环境
程序的执行过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
以上就是关于程序环境的相关内容了,如果觉得写的不错还请大家多多点赞啊哈哈!