C语⾔gcc编译过程以及常⽤编译选项
上篇⽂章知道了C代码编译后存放在内存中的位置,那么C代码的整个编译过程⼜是怎样的呢?⼀条命令gcc hello.c就可以编译成可执⾏程序a.out,然后./a.out之后就可以执⾏hello.c这个程序的代码了。下⾯的⽂章分析的不错,就整理了下。
hello.c:
#include<stdio.h>
int main()
{
printf(“Hello World\n”);
return 0;
}
实际上gcc hello.c可以分解为4个步骤,分别是预处理(Preprocess),编译(Compilation),汇编(A
ssembly)和链接(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\n");
return 0;
linux下gcc编译的四个步骤}
预处理过程主要处理规则如下:
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\n");
#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
表⽰编译为位置独⽴的代码,不⽤此选项的话编译后的代码是位置相关的所以动态载⼊时是通过代码拷贝的⽅式来满⾜不同进程的需要,⽽不能达到真正代码段共享的⽬的。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。