转自:http://blog.csdn.net/eastmoon502136/article/details/8162626
版权声明:本文为博主东月之神原创文章,未经博主允许不得转载。
上篇文章,知道了,C代码编译后存放在内存中的位置,那么C代码的整个编译过程又是怎样的呢?一条命令gcc hello.c就可以编译成可执行程序a.out,然后./a.out之后就可以执行hello.c这个程序的代码了。下面的文章分析的不错,就整理了下。
hello.c:
- #include<stdio.h>
- int main()
- {
- printf(“Hello World ”);
- return 0;
- }
实际上gcc hello.c可以分解为4个步骤,分别是预处理(Preprocess),编译(Compilation),汇编(Assembly)和链接(Linking)。
一、预处理
预处理过程主要读取c源程序,对伪指令和特殊符号进行处理。包括宏,条件编译,包含的头文件,以及一些特殊符号。基本上是一个replace的过程。
- gcc –E hello.c –o hello.i
以下为预处理后的输出文件hello.i的内容
- # 1"hello.c"
- # 1"<built-in>"
- # 1"<command-line>"
- # 1"hello.c"
- # 1 "/usr/include/stdio.h"1 3 4
- # 28"/usr/include/stdio.h" 3 4
- /***** 省略了部分内容,包括stdio.h中的一些声明及定义 *****/
- # 2"hello.c" 2
- int main()
- {
- printf("Hello World ");
- return 0;
- }
预处理过程主要处理规则如下:
1、将所有的#define删除,并且展开所有的宏定义;
2、处理所有条件编译指令,如#if,#ifdef等;
3、处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
4、删除所有的注释//和 /**/;
5、添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
6、保留所有的#pragma编译器指令,因为编译器须要使用它们;
二、编译
编译过程通过词法和语法分析,确认所有指令符合语法规则(否则报编译错),之后翻译成对应的中间码,在Linux中被称为RTL(Register Transfer Language),通常是平台无关的,这个过程也被称为编译前端。编译后端对RTL树进行裁减,优化,得到在目标机上可执行的汇编代码。gcc采用as作为其汇编器,所以汇编码是AT&T格式的,而不是Intel格式,所以在用gcc编译嵌入式汇编时,也要采用AT&T格式。
- gcc –S hello.i –o hello.s
以下为编译后的输出文件hello.s的内容
- .file "hello.c"
- .section .rodata
- .LC0:
- .string "HelloWorld"
- .text
- .globl main
- .type main, @function
- main:
- pushl %ebp
- movl %esp, %ebp
- andl $-16, %esp
- subl $16, %esp
- movl $.LC0, (%esp)
- call puts
- movl $0, %eax
- leave
- ret
- .size main, .-main
- .ident "GCC: (GNU)4.4.0 20090506 (Red Hat 4.4.0-4)"
- .section .note.GNU-stack,"",@progbits
三、汇编
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
- gcc –c hello.c –o hello.o
由于hello.o的内容为机器码,不能以文本形式方便的呈现。
四、链接
链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。
- ld –static crt1.o crti.o crtbeginT.ohello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o
(省略了文件的路径名)。
当然链接的时候还会用到静态链接库,和动态连接库。静态库和动态库都是.o目标文件的集合。
静态库是在链接过程中将相关代码提取出来加入可执行文件的库(即在链接的时候将函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中),ar只是将一些别的文件集合到一个文件中。可以打包,当然也可以解包。
- ar -v -q test.a test.o
上面指令可以生成静态链接库test.a
动态库在链接时只创建一些符号表,而在运行的时候才将有关库的代码装入内存,映射到运行时相应进程的虚地址空间。如果出错,如找不到对应的.so文件,会在执行的时候报动态连接错(可用LD_LIBRARY_PATH指定路径)。用file test.so可以看到test.so是shared object的ELF文件。
- gcc -sharedtest.so test.o
上面指令可以生成动态连接库test.so
好了,整个编译过程就如上所示了,那么对于gcc还有一些编译的选项的。具体如下:
GCC编译选项
1. -c
编译产生对象文件(*.obj)而不链接成可执行文件,当编译几个独立的模块,而待以后由链接程序把它们链接在一起时,就可以使用这个选项,如:
- gcc -c hello.c ===> hello.o
- gcc hello.o
2. -o
允许用户指定输出文件名,如
- gcc hello.c -o hello.o
- or
- gcc hello.c -o hello
3. -g
指明编译程序在编译的输出中应产生调试信息.这个调试信息使源代码和变量名引用在调试程序中或者当程序异常退出后在分析core文件时可被使用.
4. -D
允许从编译程序命令行定义宏符号
一共有两种情况:一种是用-DMACRO,相当于在程序中使用#define MACRO,另一种是用-DMACRO=A,相当于程序中的#define MACRO A.如对下面这代码:
- #ifdef DEBUG
- printf("debugmessage ");
- #endif
编译时可加上-DDEBUG参数,执行程序则打印出编译信息
5. -I
可指定查找include文件的其他位置.例如,如果有些include文件位于比较特殊的地方,比如/usr/local/include,就可以增加此选项如下:
- gcc -c -I/usr/local/include -I/opt/include hello.c
此时目录搜索会按给出的次序进行.
6. -E
这个选项是相对标准的,它允许修改命令行以使编译程序把预先处理的C文件发到标准输出,而不实际编译代码.在查看C预处理伪指令和C宏时,这是很有用的.可能的编译输出可重新定向到一个文件,然后用编辑程序来分析:
- gcc -c -E hello.c >cpp.out
此命令使include文件和程序被预先处理并重定向到文件cpp.out.以后可以用编辑程或者分页命令分析这个文件,并确定最终的C语言代码看起来如何.
7. -O
优化选项,这个选项不是标准的
-O和 -O1指定1级优化
-O2 指定2级优化
-O3 指定3级优化
-O0指定不优化
gcc -c O3 -O0 hello.c
当出现多个优化时,以最后一个为准!!
8. -Wall
以最高级别使用GNU编译程序,专门用于显示警告用!!
- gcc -Wall hello.c
9. -L
指定连接库的搜索目录,-l(小写L)指定连接库的名字
- gcc main.o -L/usr/lib -lqt -o hello
10.-share
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库
11.-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库
12.-fPIC
表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。