想听听 .c 到 .exe 的故事吗?

简介: 想听听 .c 到 .exe 的故事吗?

image.png

“ 石配树而华 ,树配石而坚 。”


前言:

在用C语言写代码前,我们会先创建一个或多个源文件(.c 文件),最终源文件会变成可执行文件(.exe 文件),你知道这期间经历了什么吗?听我细细讲解 ~



目录

🥰Part1. 程序的翻译环境和执行环境

😛Part2. 编译与链接

1.翻译环境

2. 编译的几个阶段

2.1 预编译

2.2 编译

2.3 汇编

3.链接



Part1. 程序的翻译环境和执行环境


 ANSI C 的任何一种实现中,存在两个不同的环境:  

第一种是翻译环境 ,在这个环境中源代码被转换为可执行的机器指令

种是执行环境 ,它用于实际执行代码

c4e124a13311bc075829121eb233c99b_7dc16ac54f794717a59d28c9f801546e.png

 简单的图示 

接下来我会给大家依次讲解这两个过程


Part2. 编译与链接


1.翻译环境

9cdc039ed1ff8ef8c343ccac420f8cfc_418bf49e551f4c69b41a62abb1b3d6cd.png

如图,每个 源文件 单独经过 编译器 处理,生成 目标文件 ,所有目标文件与 链接库 一起,在 链接器 的作用下生成 可执行文件

演示:

创建一个项目,在这个项目下创建三个源文件

030f467f0e92fc93fa8998c0a1e18eaa_4050f94e5a6a41c084208045ee11b9f8.png

内容如下:

//test.c
#include<stdio.h>
extern Add(int, int);
extern Sub(int, int);
int main()
{
  int a = 20;
  int b = 30;
  int c = Add(a, b);
  int d = Sub(a, b);
  printf("%d\n", c);
  printf("%d\n", d);
  return 0;
}
//Add.c
int Sub(int x, int y)
{
  return x + y;
}
//Sub.c
int Add(int x, int y)
{
  return x - y;
}

运行后,

查看 .exe 文件:

f439da8f388202611f35198b01b7e89d_1d7611facdc1424ca7be24da52c43268.png

查看 .obj 文件:

81bf140888474ebf2bd8c4099d865397_e5944eb89c1742cc92f60fb92a117948.png

12efe8369d2aa884b6bbe3fa669a2018_e0fa35aab5534330be2c86a9632ef700.png

是不是与上述过程一样呢 

一些细节:

• 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code);

• 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序;

• 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。


2. 编译的几个阶段


我们已经知道了,源文件变成可执行文件需要编译和链接两个过程,那么编译本身有哪些阶段呢?

ec6ecd44b8a8a80df8ee112b4f848736_248f15607b904c2b80720cab9a880b86.png

我是图示

由图示可看出:编译经历的阶段有 预编译 --> 编译 --> 汇编

接下来细讲每个过程:

注:由于 VS2022 属于集成开发环境,不方便展示细节,

      所以接下来我会使用 Linux 环境下的 gcc 来演示编译和链接的过程。

为方便起见,我们只使用 test.c 和 Add.c 文件


2.1 预编译


首先把刚才的代码写入文件:

59aa831cbe5dd4ef4955f8801f4eaaa3_1b97b0f1531245ba979e529b58123906.png

这时我们只进行预编译

输入指令: gcc test.c -E (-E 就是预编译后停下来)

回车

会发现 预编译的结果直接输出在屏幕上了:

171ef1167849284c85bb06c8876b9fcb_156fcfb7c67c4bfe87426bd2556dbbf3.png

其实内容非常多,这里选取部分

我希望把 预编译的内容放在一个文件中

输入指令: gcc test.c -E -o test.i

644130c442a264dfd32e1cded45890a2_beed7c7efd744a40856e40c5bf0c3067.png

此时就多了一个 test.i 文件

对 Add.c 进行相同的操作   gcc Add.c -E -o Add.i

打开相应的文件 vim test.i

有足足800多行代码:

6e3b00de433ebd4e686661510b17f9f9_a25b25ad38604306b666a1b343054bc9.png

这时会问了:为什么预编译后出现了这么多代码?

回想一下,我们的 test.c 里面有什么内容来着?

#include<stdio.h>

没错,就是引了头文件,可以理解为: 在预编译过程中将相关的头文件进行了展开

于是就有了预编译的第一条操作:

• 头文件的包含

接下来我在 test.c 文件中加入以下代码:

ac95afe9c82438cd49c9c1a49e87b3e9_940982ea58624b5d9ba58872f9b4d590.png

重新预编译 test.c 到 test.i 中:

69fc2b496b258dfca0bb948903fc5259_c8fccd58eef24ed9b038bf39335d0ed4.png

可以发现:MAX被替换,注释也消失了;

• define 定义符号的替换

• 注释删除

小总结:

预编译阶段中进行的操作:

• 头文件的包含

• define 定义符号的替换

• 注释删除

     都属于 文本操作


2.2 编译


我们接下来要做的就是让预编译后的文件只进行编译操作

输入指令: gcc test.i -S

93f8b2667e8aee8b6ecacf074a0b94ed_8dcd33d69e314998beb68d568a7f487c.png

可见生成了一个 test.s 的文件

打开 test.s :

61e2ab6fecddb1623022c9531095a6f8_73a7adeaf0c34c5dbab731c112e1a2c0.png

可见里面是一些 汇编代码

其实这就是编译阶段的主要操作:

• 把C语言代码翻译成汇编代码

再深入些,

翻译的过程有:

1. 语法分析

2. 词法分析

3. 语义分析

4. 符号汇总

其实前三个过程比较好理解,就像我们读英语一样,要把英语转换成中文来理解。像这些分析,其实就是把C语言代码转换成汇编能理解的代码。

符号汇总就比较特殊,这里拿出细讲:

符号汇总,其实就是 把一个文件中那些全局的符号汇总到一起

比如:

0f6576b0478ffb509df09d2b1e7bb74a_2da9b8a63a8948889b3c4009b26ee9f0.png红色方块部分是汇总出的符号

某种意义上,汇总的符号是函数的符号。那这有什么用呢?先不急,接着看。


2.3 汇编


进一步,令编译后的文件只进行汇编操作

输入指令: gcc test.s -c

fce065ceb48b3d3af95069fe639d8009_989e9791274e4c5daaa7f013325676fa.png又出现了新的 test.o 文件

打开 test.o :

image.png

嗯,看不懂。

其实是 二进制 啦 ~ 当然看不懂

那么 汇编的操作 也显而易见了:

• 把汇编指令翻译成二进制指令

其中又有一个重要的过程: 形成符号表

我们说了看不懂,那谁能帮我们看懂呢?

在 Linux环境下:

test.o 和可执行文件的格式是 elf ,理应, readelf 可以读

输入命令: readelf test.o -s

2ccb565a776bb84aa81e2b21a58378cf_bf111f8b518d425a8f4740ce475ceb4b.png

这不,汇总的符号就出现了。

1e006c8171d1c9f6cb131b8c6e6dc430_ad08f608e4ce4634a7ddc035d504cc50.png

形成符号表,其实就是把汇总出的符号赋予一个临时地址


3.链接


链接链接,通俗来讲就是把多个文件连接起来

链接进行的操作:

• 合并段表

• 符号表的合并和重定位

合并段表解释:

前面提到 .o 文件和可执行文件都是 elf 格式,elf 格式本身具有段,

.o 文件在形成可执行文件的过程中会把相对应的段合并

c9bdb659326c92bc3e2f352e4f6f4093_5e8372d540774d7290c27803fee4cd2f.png

                                          图示

符号表的合并和重定位:

前面提到了形成符号表,并赋予了一个临时地址,

它们最终会 合并 到一起

对于出现一次的符号来说,地址可以直接用临时赋予的地址;

而对于出现多次的符号来说,地址不统一,就需要 重新定位

e4eaa91d0129a5ae8c97d9f322c2427b_4dd121fcd4024a9aa9f749bd25d247cc.png

                                            图示


总结:

听了 .c 到 .exe 的故事,是不是感觉 .c 到 .exe 道路有些艰辛呢?

2b2544f826419a9cde2b589ab0cdf63e_ad73846a0ce44a598b4cf2b9078bcdfc.png一图总结



目录
相关文章
|
7月前
|
缓存 安全 网络协议
花无涯带你走进黑客世界7 Dos命令
为什么电影中的黑客几乎绝不用鼠标,都是键盘一通乱敲?第七章,开始实战各方面的软件安装和平台的了解, 把术语讲清楚慢慢一步步的来过渡了解与学习,帮助更多人学习到黑客攻防,学会保护自己和身边的人,主要是谈谈小白如何慢慢学习网络安全相关知识,有正确得价值观,做正确的事情,欢迎各界人士给建议。相信每一个对计算机感兴趣的童鞋都有着一颗黑客的心,我希望通过一系列的文章让大家了解黑客和网络安全。
|
数据安全/隐私保护 图形学 Windows
推荐五款宝藏软件,身为宝藏男孩和宝藏女孩的你,不试一下吗?
今天带来五款宝藏软件,身为宝藏男孩和宝藏女孩的你们,不试一下吗?
153 0
推荐五款宝藏软件,身为宝藏男孩和宝藏女孩的你,不试一下吗?
|
运维 Java Shell
shell基础、脚本设计与运行(少年没有乌托邦,心向远方自明朗)
shell基础、脚本设计与运行(少年没有乌托邦,心向远方自明朗)
128 0
shell基础、脚本设计与运行(少年没有乌托邦,心向远方自明朗)
|
算法 Unix 编译器
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(中)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
414 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(中)
|
机器学习/深度学习 JavaScript 前端开发
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(下)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
155 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(下)
|
算法 编译器 程序员
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(上)
刚刚,2020年图灵奖揭晓!影响了数代人的「龙书」作者——阿尔佛雷德·艾侯 (Alfred Aho)和杰弗里·戴维·乌尔曼(Jeffrey David Ullman)获奖。
167 0
哭了!2020图灵奖颁给编程的回忆——Jeff Dean 的编译启蒙书(上)
|
程序员
一个BAT老程序员的忠告!
​ 一、在中国,你千万不要因为学习技术就可以换来稳定的生活和高的薪水待遇,你更不要认为那些从事市场、运营的人,没有前途。 不清楚你是不是知道,咱们中国有相当大的一部分软件公司,他们的软件开发团队都小的可怜,甚至只有1-3个人,连一个项目小组都算不上,而这样的团队却要承担一个软件公司所有的软件开发任务,在软件上线和开发的关键阶段需要团队的成员没日没夜的加班,还需要为测试出的BUG和不能按时提交的软件模块功能而心怀忐忑。