C/C++程序编译链接原理(3)----从编译器角度

一段代码在编译器上是如何生成可执行文件的呢,从编译器角度看,生成可执行文件需要经过的阶段有四个:预编译、编译、汇编、链接,在这四个过程中所做的工作各不相同,在gcc和g++编译器下我们也可以逐步执行来看下每个阶段的生成文件。下来以两段代码共同链接生成可执行文件的过程来详细说明:

//main.cpp

//引用sum.cpp文件里定义的全局变量以及函数
#define max 20
extern int gdata;
int sum(int ,int );

int data=20;

int main()
{
    
    
	int a=gdata;
	int b=data;
	int test=max;
	int ret=sum(a,b);
	return 0;
}
//sum.cpp
int gdata=10;
int sum(int a,int b)
{
    
    
	return a+b;
}

一、预编译阶段

预编译阶段所做的工作主要为:

1、处理预编译指令:在上一个博客中说了预编译指令的种类,可以作为参考;关于预编译指令
2、头文件的展开:将头文件的内容在代码处展开;
3、宏的替换:将使用宏的地方全部替换成宏定义后的数值;
4、删除注释:删除所有的注释,替换为空格。

预编译阶段生成的文件

1、预编译阶段生成.i文件
2、在gcc下我们使用如下命令来生成.i文件:gcc -E 源文件名 -o 源文件名.i
3、可以使用cat命令来查看生成.i文件的内容,例如上面的main.cpp:
在这里插入图片描述
可以看到宏已经替换,注释也删除了。

二、编译阶段

编译阶段所做的工作主要为:

1、词法语法解析:这部分的内容极为庞杂,在学习的时候找到了这篇博客,可以直接看这篇博客的,写的很详细 援引别人的博客,写的很细致《编译和链接的过程》
2、代码优化:同样,这部分也很复杂,看下面这个博客(不是偷懒,是真的感觉自己总结不了那么细致,哈哈哈)代码优化
3、生成符号:在第一个编译链接原理中我们说了变量生成符号,其实不止变量,函数也是会生成符号的,生成符号就是在这个阶段执行的。
4、生成汇编指令:将代码全部转换为汇编指令。

编译阶段生成的文件

1、编译阶段生成.s文件;
2、gcc下的指令:gcc -S main.i
3、生成的.s文件中包含的都是汇编指令,以下是生成的符号以及汇编指令(截取部分)
在这里插入图片描述

三、汇编阶段

汇编阶段所做的工作主要为:

1、将汇编指令翻译成适合平台的二进制指令;
2、生成各个section段;
3、生成符号表。

汇编阶段生成的文件

1、汇编阶段生成.o文件,即可重定位的二进制目标文件,windows下生成.obj文件;因为在编译阶段各个文件生成的符号都是各个文件中生成的,在下一阶段需要将各个符号重新定位到定义部分;另外,此部分的文件带有ELF文件头;
2、gcc的指令:gcc -c main.s
3、生成的文件都是二进制文件,所以无法使用普通查看命令来查看。

对生成的.o文件的操作

生成的.o文件我们可以使用objdump命令来查看其文件信息,在这里我们只使用几个简单的,如果需要了解objdump命令,请戳:objdump命令
1、查看文件的段信息:objdump -h main.o
在这里插入图片描述

2、查看文件的符号表信息:objdump -t main.o
在这里插入图片描述
符号的类型:
(1)l和g所代表的含义:l代表local,只能在当前文件中可见,g代表global,可以在本工程的所有文件中可见;
(2)我们可以看到gdata前部分有* UND *,代表这个变量并没有在本文件中定义,是引用外部文件的变量。
(3)结合符号表和段表的结果我们可以看到前两个博客的结果是可以被证明的;
(4)编译过程中符号是不分配地址的。
因为.o文件是由ELF文件头和各个段组成的,我们可以使用readelf命令来查看.o文件的ELF段:
(1)readelf -h main.o:用以查看文件信息
在这里插入图片描述
(2)readelf -S main.o:查看段信息
在这里插入图片描述

四、链接阶段

链接阶段链接器所做的工作:

1、合并所有文件的段表,合并后大小会变,所以需要调整段的大小和起始位置;
2、合并所有文件的符号表;
3、解析符号:解析所有GLOBAL符号,所有对符号的引用(* UND *)都要找到其定义的地方;若有未定义或者重复定义都会报错;
4、符号重定位:符号解析成功后给所有符号分配虚拟地址,并将分配的地址写入汇编代码需要地址的地方;
5、链接所有需要的静态库文件。

链接生成的文件

1、ld -e main *.o
使用ld命令来将所有文件链接起来,-e main用来指定入口函数为main,使用通配符来代表所有的.o文件;
2、不指定生成文件名时默认生成a.out可执行文件,此文件和可重定位的二进制文件的组成基本是相同的,也是由ELF文件头和各个段组成,同样我们也可以使用objdump查看他的各个段;
(1)objdump -h a.out
在这里插入图片描述
(2)objdump -t a.out
在这里插入图片描述
3、对可执行文件也可以使用readelf命令查看其ELF文件头的信息:
(1)readelf -h a.out
在这里插入图片描述

(2)readelf -S a.out
在这里插入图片描述
可以看到文件中各个段已经定位;
4、在可执行文件中添加了pragram段,用于告知需要加载的文件。

五、可执行文件的组成格式

首先是一个ELF段:用于告知程序的入口地址,以及存放文件的版本信息;再是一个program段,告知需要加载的文件;其余的段和虚拟地址空间的段大同小异了。而在gcc下我们可以使用如下命令直接生成可执行文件:gcc -o main.c main,指定了生成的可执行文件为main。或者使用命令:gcc main.o -o main通过.o文件来生成可执行文件。

以梦为马,不负韶华。

猜你喜欢

转载自blog.csdn.net/qq_45132647/article/details/105707175