最近在参加提前批的面试,今天面的是vivo的嵌入式。其中问到一个问题,c++的源程序到生成可执行文件的过程。喵哥吞吞吐吐的答出了一部分。现在面完了,在网上找了下标准答案:预处理、编译、汇编、链接。
在Windows下,VC编译一个源程序都是一键完成,不知道过程,而我用Linux的gcc生成执行文件时,都是用
g++ hello.cpp -o test
然后用
./test
执行。原来我给自己挖了这么久的坑啊,完全省去了中间步骤。
test用vim打开的结果是:
如果按照上面提到的四个步骤处理源文件,过程如下。
1.预处理
g++ -E hello.cpp -o hello.i
vim打开hello.i的结果
2.编译
g++ -S hello.i -o hello.s
用vim打开hello.s
3.汇编
g++ -c hello.s -o hello.o
用vim打开hello.o
4.链接
g++ hello.o -o hello
用vim打开hello
于是,发现hello跟test是一样的,但是我之前都是一步生成可执行文件,所以对这个过程不是太了解。
在生成过程中产生的文件也就只有最终的可执行文件可以执行(下面第二幅图中绿色高亮的是可执行文件)。
- 预处理:gcc –E hello.c –o hello.i;gcc –E调用cpp
- 编 译:gcc –S hello.i –o hello.s;gcc –S调用ccl
- 汇 编:gcc –c hello.s –o hello.o;gcc -c 调用as
- 链 接:gcc hello.o –o hello ;gcc -o 调用ld
链接可以执行与编译时(源代码被翻译成机器代码时),也可以执行与加载时(在程序被加载器加载到存储器并执行时),甚至执行与运行时,由应用程序来执行。在现代系统中,链接是由链接器自动执行的。链接器分为:静态链接器和动态链接器两种。
a. 静态链接器
静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。
静态链接器主要完成两个任务:
(1)符号解析:目标文件定义和引用符号。符号解析的目的在于将每个符号引用和一个符号定义联系起来。
(2)重定位:编译器和汇编器生成从地址零开始的代码和数据节。链接器通过把每个符号定义和一个存储器位置联系起来,然后修改所有对这些符号的引用,使得他们执行这个存储位置,从而重定位这些节。
b. 动态链接
动态链接方式下,函数的定义在动态链接库或共享对象的目标文件中。在编译的链接阶段,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器链接程序在运行过程中根据记录的共享对象的符号定义来动态加载共享库,然后完成重定位。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
目标文件有三种形式:
1> 可重定位的目标文件:
包含二进制代码和数据,其形式可以再编译时与其他可定位目标文件合并起来,创建一个可执行目标文件。
2> 可执行目标文件:
包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。
3> 共享目标文件:
一种特殊的可重定位目标文件,可以再加载或运行时,被动态地夹在到存储器并执行。编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件。
gcc优化的方式:
1. -O0:无优化(默认)。
2. -O1:使用能减少目标文件大小以及执行时间并且不会使编译时间明显增加的优化.在编译大型程序的时候会显著增加编译时内存的使用。
3. -O2: 包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化.编译器不执行循环展开以及函数内联.此选项将增加编译时间和目标文件的执行性能。(一般优化到-O2就可以了)
4. -Os:专门优化目标文件大小,执行所有的不增加目标文件大小的-O2优化选项.并且执行专门减小目标文件大小的优化选项。
5. -O3: 打开所有-O2的优化选项并且增加 -finline-functions, -funswitch-loops,-fpredictive-commoning, -fgcse-after-reload and -ftree-vectorize优化选项。
2019.07.17
对于执行过程中的文件信息读取,可以使用file 文件名、binutils的objdump -h 文件名 查看文件,对于ELF文件还可以用readelf -a 文件名 查看。