GCC编译器总结

0.108字数 1501阅读 3864

GCC简介

1、GCC经过那么多年的发展,已经从最初的C编译器转变成了编译器的集合,官方定义是GNU Complier Collection,现在的GCC不仅支持C还支持C++、Java等语言。
2、GCC是一个编译系统的驱动程序,负责解析输入的参数,依次调用预处理器(cpp)、编译器(ccl/cclplus)、汇编器(as)、链接器(ld)生成可执行文件。
3、GCC 和 G++ 的区别并不是前者用来编译C代码,后者用来编译 C++ 代码。它们的区别是GCC把后缀为c的文件当C代码处理(ccl编译),而 G++ 则当作 C++ 处理(cclplus编译),对于后缀为cpp的文件gcc和 g++ 处理过程没有什么区别。GCC默认是不能编译 C++ 程序的,需要加上-lstdc++选项,G++ 是可以直接编译的,G++相当于是对GCC的封装。

GCC编译过程

在介绍GCC编译步骤之前,首先需要了解GCC支持的后缀文件类型。

后缀名 对应语言
.c C程序
.C/.cc/.cxx C++程序
.i 预处理后的C程序
.ii 预处理后的C++程序
.s/.S 汇编语言程序
.h 头文件
.o 目标文件
.a/.so 编译后的库文件

GCC编译流程分为四个步骤:预处理(生成.i/.ii文件)、编译(生成.s/.S文件)、汇编(生成.o文件)、链接(生成可执行文件)。


gcc指令的一般格式为:
gcc [选项] 要编译的文件 [选项] [目标文件]
其中,目标文件可缺省,gcc默认生成可执行的文件为a.out


#include <stdio.h>
int main()
{
    printf("Hello World\n");
    return 0;
}

1、预处理
对于该阶段,gcc将stdio.h文件中的代码包含进这段程序,我们可以利用gcc -E test.c -o test.i命令来生成预处理过的.i文件。-E选项代表让gcc在预处理阶段后停止编译。test.i文件中的内容如下所示,可以看出stdio.h文件中的内容被展开。

extern int fprintf (FILE *__restrict __stream,
  __const char *__restrict __format, ...);
  ......
  # 8 "test.c" 2
int main()
{
    printf("Hello World\n");
    return 0;
}

2、编译
该阶段主要是对预编译后的.i文件编译,生成汇编代码的.s文件。gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。我们可以使用-S选项来进行查看,该选项只进行编译而不进行汇编过程,生成汇编代码。
我们可以利用gcc -S test.i -o test.s命令进行编译过程。test.s文件中的内容如下所示。

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "Hello World"
    .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.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

3、汇编
该阶段是将编译后的.s文件转化成二进制文件.o的过程,利用-c选项就可以生成二进制.o文件。我们可以利用gcc -c test.s -o test.o生成二进制代码。
4、链接


该阶段主要将成功编译的二进制文件进行链接操作,生成可执行文件。利用gcc test.o -o test生成可执行文件test,运行./test即可打印出hello world


总结一下:

gcc -E  test.c -o test.i    //把原代码交给cpp预处理器生成经过预处理后的中间文件test.i
gcc -S test.i -o test.s     //把经过预处理之后的test.i文件交给编译器cc1生成test.s文件
gcc -c test.s -o test.o     //把经过编译后汇编文件test.s交给as进行汇编,生成目标test.o文件
gcc test.o -o test          //将as汇编之后的目标文件交给ld链接成一个可执行的文件test

GCC常用编译选项

1、总体编译选项

选项名称 作用
-c 只是编译不链接,生成目标文件.o
-S 只是编译不汇编,生成汇编代码
-E 只进行预编译,不做其他处理
-g 在可执行程序中包含标准调试信息
-o file 把输出文件输出到file里
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
-L dir 在库文件的搜索路径列表中添加dir目录
-static 链接静态库
-llibrary 连接名为library的库文件

表中前边几个选项在GCC编译步骤中已经有所了解,主要对表中后四个选项进行介绍。
当我们进行程序开发时,基本都会用到很多函数库,从程序员的角度看,函数库就是封装的一些头文件(.h)和库文件(.a/.so),Linux系统下头文件一般默认放到/usr/include/目录下,库文件放在/usr/lib/目录下,如果我们使用的库不在标准路径下,我们就需要指定头文件和库文件的路径以便让gcc找到它们。
举个例子,假设上一节test.c主要实现C语言连接mysql数据库的功能,我们从官网下载的mysql connectors的头文件在/usr/local/mysq/include/路径下,库函数在/usr/local/mysql/lib/路径下。
我们可以利用

gcc -c -I /usr/local/mysql/include test.c -o test.o

生成test.o二进制文件,再利用

gcc -L /usr/local/mysql/lib -lmysqlclient test.o -o test

进行链接操作产生可执行的test文件。这两步也可以合成一步,直接生成可执行的test文件

gcc -I /usr/local/mysql/include -L /usr/local/mysql/lib -lmysqlclient test.c -o test

Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要使用静态链接库可以在编译时加上-static选项,强制使用静态链接库。
比如在/usr/local/mysql/lib目录下有链接时所需要的库文件libmysqlclient.so和libmysqlclient.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/local/mysql/lib -static –lmysqlclient test.o –o test

需要注意的是:

  • -I dir-L dir都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。
  • 另外值得详细解释一下的是-l选项, 它指示gcc去连接库文件libXXX.so。由于在Linux下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库文件名时可以省去lib三个字母。也就是说gcc在对”-lXXX”进行处理时,会自动去链接名为libXXX.so的文件。

2、其他常用编译选项

选项名称 作用
-ansi 支持符合ANSI标准的C程序
-pedantic 允许发出ANSI C标准所列的全部警告信息
-pedantic-error 允许发出ANSI C标准所列的全部错误信息
-Wall 允许发出gcc提供的所有有用的报警信息
-Werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程
-O 主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化
-O2 除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等
-O3 还包括循环展开和其他一些与处理器特性相关的优化工作
-static 指示链接器构建一个完全链接的可执行程序,即链接静态库而不链接动态库
-fPIC 指示链接器创建一个共享的目标文件,即so文件
-shared 生成动态库,一般和上面的-fPIC一起使用

参考文献:


GCC学习总结

Linux GCC常用命令

GCC 编译详解

推荐阅读更多精彩内容