C语言代码编译过程藏着这么多秘密?原来小丑是我自己!

C语言必须写main函数?最简单的 Hello world 你其实一点都不懂!

在第一讲,我们用了gcc -o hello hello.c,然后可以加-v,来看编译过程,我们可以看到关键的几个节点:

这里截取的是编译,汇编的两个标记了出来,截图的最后,是链接的过程,这个输出里面,没有看到预处理的这个环节。

我们今天来详细的说下编译经过的四个过程,以及每个过程的输出,让大家对这个有直观的认识。

总共四个步骤:

1 预处理

2 编译

3 汇编

4 链接

预处理不是编译器的组成部分,但是它是编译过程的一个独立的部分,可以理解成编译前的准备工作,把一些方便我们编写的定义进行展开,替换。

预处理命令,是以#开头的,我们常用的 #include <xxxx.h>,就属于这个范畴。还有一些我们常见的就是

#define PI 3.14159

就是让源码中的PI,都用3.14159替换。我们看下全部的预处理指令,在大型项目上,这些都能看到的。

而关于这里面的#,##,我们单独花一节时间来讲下,这块在扩展的时候非常有用,C里面用这个来做基类的,也就可以实现跟C++的一样,有父类的概念。

我们今天把代码换回最基础的,来看看这个编译到可执行的每个步骤的输出。

1 预处理命令:gcc -E hello.c -o hello.i 。预处理的常见动作:

  • 预留#pragma编译器指令,随后编译器依据这个输出信息,或者报错,常见使用的是报错,方便定位。

  • 将所用的#define删除掉,用定义的去展开定义。

  • 处理条件预编译指令,这里就是#if #elif #else #endif ,如果是只判定定义,还有#ifdef 这类。

  • 删除注释 // 和/* */。

  • 处理#include,进行展开,替换,插入输出文件中。

  • 加入行号和文件信息,编译时候可以提示出错位置。

我们看下这里输出的hello.i ,基本就能理解。

这里前面的代表行号,哪个文件的第几行,然后每一行后面的数字,这里我们使用官网的看下就明白了。

https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html

1:表示新文件的开始。

2:表示返回到文件(包含另一个文件后)

3:这表示以下文本来自系统头文件,因此应禁止某些警告。

4:这表示应将以下文本视为包含在隐式 extern"C"块中。

这就是我们的预处理,将我们依赖的,源码先进行一次处理,然后交给编译器。

2 编译 gcc -S hello.i -o hello.s ,将i变成汇编语言。我们看下输出结果:

关于汇编中的各种参数,段定义声明,参考http://web.mit.edu/rhel-doc/3/rhel-as-en-3/

这个过程就是进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码,对应的就是我们学习的编译原理这本书籍,对语法进行判断,如果出错就会提示哪个文件,哪块写错了。

这两步骤,都是让我们更容易看懂,我们不断的靠近最终的机器语言,汇编语言下来的就是指令,这里的过程,就是进行对应,我们把这个叫做指令表。

在起初写代码,使用的是打孔,就是纯的机器语言,而后大家为了方便记忆,做了一些翻译,我们真正的机器,是有一个运算器,运算器的位数,最终能决定它的指令集,这个指令集是由硬件决定的,而这个指令集的数目,一定程度上代表计算机的运算能力。

我们可以查找X86汇编指令集,或者ARM汇编指令集。现在我们不需要痛苦的去写机器码,而是写汇编指令,通过汇编器,让它帮我们翻译,生成最终的机器码。这里找一个表格,大家就知道了,就是一个简单的标记,方便我们写代码。

那么下一步就是我们把这种mov,给它变成机器可以理解的0101这种机器码。

3 汇编 gcc -c hello.s -o hello.o,这个变成了机器码,目标文件,Linux下具体的就是ELF格式。静态库,动态库,可执行文件都是ELF。

当我们汇编成.o,为什么不能执行,因为这里面我们使用了extern 引用了printf函数,这个函数是在别的.o里面的。我们经常用的是静态库,也就是.a,.a就是.o的合集,是把一堆.o合并在一起,如果我们需要.o,可以使用ar进行拆解。

这里我们使用readelf -s hello.o 来看下符号,发现了有个puts 的UND,UND代表的未定义,如果我们不找到,编译链接的时候就会出错,提示找不到puts,也就是未定义的符号。

printf,如果只是输出子串,会替换成puts,因为这个轻量,系统的优化。

4 链接,gcc hello.o -o hello ,这里我们用-v查看下编译的指令,可以看到有几个有趣的链接,crtbegin 和crtend ,这个就是在我们的程序运行前,系统给的入口,要做堆栈初始化。

这里我们可以看到系统的路径,同时我们这里看到了-lc ,这里就是链接时候需要找libc.a,这个里面有它需要的一些函数实现,这里具体的就是puts,我们如何判断呢?

我们可以使用ar -x libc.a ,然后拿到ioputs.o,进行readelf -s ioputs.o,就可以看到有puts这个实现的。想要仔细研究这些内容,可以用IDA6.1破解版,加载这些文件,然后就能看到关键信息,主要就是导入导出符号表,这个非常关键。

如果你想在裸机上面跑程序,这里说的是嵌入式板子,那么这里的编译之后的ELF可执行文件格式,在上面是没法运行的,原因是裸机上面没有可以解析ELF的解析器,所以我们在这类机子上,还需要做一个操作,就是转化成bin文件,也就是去掉ELF的文件头,不需要解析就能执行的纯指令文档。

我们这里使用objcopy -O binary hello hello.bin ,转化成bin,转化完了之后,再用readelf解析,就会提示找不到对应的幻数,解析不了。

好了这一节就说到这里,我们记住步骤就是,预处理,编译,汇编,链接。每个步骤都有其关键的动作。下一节我们来说下指针,如果把它轻松的拿下来?

喜欢,帮忙转发~~

~~ end not end ~~

热门文章

零基础新手自学Python编程教程入门精通学习资料网站大全

自学编程C语言不迷路,我私藏的书单分享给你!

零基础新手学习算法Leetcode刷题指南

程序员码农IT工程师自学编程计算机入门进阶学习网站大全

程序员面试题宝典以及相关书籍下载!

计算机类常用电子书整理大全

职场老鸟,互联网十年从业生涯,分享 [Java,Python,安卓,AI,爬虫] 技术文章,学习资料, 热点趣闻等。关注回复 1024 Python 电子书大全 面试资料,给你一份私藏的程序员好礼,永远更新中!赶紧来关注哦!

我的微信 code_gg_boy 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员入门进阶(免费辅导开题报告)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值