在讲解.C代码变成可执行文件的过程之前先来简单介绍汇编。
比如有个汇编语句:
mov ax,bx
计算机执行这条指令的含义是把bx寄存器里面的数据放入ax寄存器里面,(ax与bx你可以先认为是CPU存放数据的地方,mov是CPU执行的一个动作),汇编是最接近机器的语言,为什么这么说,
在芯片公司会有一份手册,对应的汇编指令与对应二进制列表
图2-1汇编指令与二进制
大家可以看到红色表示对应汇编指令,黄色表示对应的二进制码。假设mov指令对应的二进制操作码是0x011 ax寄存器二进制为0x1 (就好比CPU认为操作码后面如果接0x1就代表ax寄存器),bx寄存器二进制为0x2,这里只是简单设置对应二进制码,实际中复杂很多。
那么对应的机器执行指令是 011 01 02 表示对应的汇编mov ax bx,在程序最开始没有汇编,很多程序员通过二进制指令进行编码,一遍查看手册一遍进行编码,这样是不是很麻烦,后面产生了汇编,对应于二进制就很大进步了,大家只要对应指令就可以进行编程了,不用再想着复杂的二进制指令了。
随着时代进步,大牛发明了C语言,比汇编更高级的语言,可是机器还是只能执行二进制文件,对吧,那么我们可以把程序从.C变成.S.然后再变成对应二进制文件,这样不就可以了么?
接着来看一个.C转为二进制文件的过程。
程序:
图2-2hello程序
这个程序的意思就是打印 hello world
现在把从代码到到执行一遍,让大家有个概念,这个gcc是把代码转为二进制文件的编译工具,这个就像你想把自来水变成开水,这个就是煮开水的,
这句话的意思是用gcc编译输入文件hello.c ,对于-o参数可以认为-output表示对应的输出的文件名为hello
图2-3hello编译
Hello.c是程序员写的程序,hello是机器的可执行程序,执行这条命令的时候怎样让程序从.c变成机器可执行程序?
图2-4程序编译过程
从程序员的自我修养里面可以看到他经过以上步骤得到对应的可执行文件,现在对这个图来具体分析
1、从红色圈里面可以看到源代码从.c变成.i文件,件这两个文件又什么区别?
可以看到对应的.c里面的函数还在,#include已经替换为对应格式,这里没有把全部数据显示出来,从.c编程.i的过程就是一个把#include里面的.h用新的格式取代,这样做的目的是为了后续给编译器进行语法分析。
(这里可以看到有对应的数字,这里不进行解释,https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html,这个网页就解释了对应数字的含义,大家在这里记得.C变成.i作用是把.c变成编译器想要的格式。)
2、 从黄色的部分可以看到进行编译器编译之后变成了.s的一个汇编文件。
这个是从.i文件编译成.s,可以看到这个就变成了一个汇编文件
3、变成汇编文件之后用汇编器就可以把文件变成对应的.o文件了
Linux有个工具可以分析elf文件,这个工具名字叫做readelf,可以看到对应的.o文件TYPE是REL类型的文件叫做可以重定向文件,这里的Entry point address可以看到是0x0,这个表示程序执行的地址,大家都是到0x0地址是NULL代表这个时候这个地址不可以执行。
大家肯定有疑问为什么.o文件已经是二进制文件了却不可以执行,以及后面的链接做了哪些才能保证文件的执行?这个在后面进行讲解
4、链接之后.o变成可执行文件
大家可以看到经过链接的.o文件之后type变成了EXEC这个文件叫做可执行文件,也就是可以执行,然后对应的Entry point adress变成了0x40040,这个就是这个可执行文件最开始执行指令的地址
大家肯定有疑问为什么.o文件已经是二进制文件了却不可以执行,以及后面的链接做了哪些才能保证文件的执行?
1、现在来看hello.o只是把hello.c里面自己定义的函数翻译成二进制,而printf 在对应的hello.o里面找不到对应的实现,所以不能把函数printf进行翻译成二进制文件。
2、链接就是把printf所在的.o文件与hello.o文件一起组合,就好像hello.o里面需要printf这个函数,那么printf所在的.o提供给他,这样所有的函数都收齐了,那么一个程序所需要的所有函数都能找到对应的实现,要开始给每个函数每一条指令规划内存的地址了,规划好地址之后就可以执行了。
这个是微信公众号