gcc编译器及编译过程

GCC编译器

GCC(GNU Compiler Collection,即 GNU 编译器套装),是一套由 GNU 开发的编程 语言编译器。它是一套以 GPL 及 LGPL 许可证所发行的自由软件,也是 GNU 计划的关键部 分,亦是自由的类 Unix 及苹果计算机 Mac OS X 操作系统的标准编译器。GCC(特别是其 中的 C 语言编译器)也常被认为是跨平台编译器的事实标准。
Linux 系统下的 GCC 编译器实际上是 GNU 编译工具链中的一款软件,可以用它来调用 其他不同的工具进行诸如预处理、编译、汇编和链接这样的工作。GCC 不仅功能强大,性 能优越,其执行效率比一般的编译器相比要高 20%~30%,而且由于其是 GNU 项目之一, 是开源的软件。

编译过程简介

程序之所以需要编译,是因为处理器不能直接理解由文本字符组成的源代码文件。
比如:

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

这个程序是一个符合 C 语言语法的文本源程序,不能指望 CPU 能像我们这样“读懂” 它,CPU 没长眼睛,它只能“读懂”像这样的二进制序列:10110101010001…,因此我 们必须将写好的文本程序编译生成一个可以被处理器直接解释的二进制指令文件,GCC 可 以帮助我们达到这个目的,具体方法如下:

gcc hello.c -o hello

上述命令的意思是:用 gcc 这个工具编译 hello.c,并且使之生成一个二进制文件 hello。 其中 –o 的意义是 output,指明要生成的文件的名称,如果不写 –o hello 的话会生成默认 的一个 a.out 文件。
编译过程:
在这里插入图片描述

1 预处理

GCC 在第一个阶段会调用预处理器 cpp 来对 C 源程序进行预处理,所谓的预处理就是 解释源程序当中的所有的预处理指令,那些诸如#include、#define、#if 等以井号’#’开头的 语句就是预处理指令,预处理指令实际上并不是 C 语言本身的组成部分,而是为了更好地 组织程序所使用的一些“预先处理的”工作,这些工作用一种叫做与处理指令的语句来描述, 然后用预处理器来解释,这些工作包括我们熟悉的诸如文件包含、宏定义、条件编译等等。
这些预处理指令将会在预处理阶段被解释掉,比如会把文件包含语句所指定 的文件拷贝进来,覆盖掉原来的#include 语句,所有的宏定义被展开(因此宏展开是不占 用运行时间的),所有的条件编译语句将被执行等。
另外,在这个预处理阶段,除了处理这些预处理指令之外,GCC 还会把程序当中的注 释删除,另外添加必要的调试信息(比如你的程序在编译时报告说第 10 行有语法错误,那 么“第 10 行”这个信息就是在这个阶段被添加进去的,显然这些信息在你的程序已经调试 完毕,要进行发布时是冗余的,因此可以用实用工具 strip 来去掉这些信息,使得程序的尺 寸变得更加紧凑)。
如果想获得C源程序经过预处理之后的文件,方法如下:

gcc hello.c -o hello.i -E

编译选项 -E 可以使得 gcc 在进行完第一阶段的预处理之后停下来,生成一个默认文件名为.i的文本文件。

2 编译

经过预处理之后生成的.i 文件依然是一个文本文件,不能被处理器直接解释,我们需要 进一步的翻译。接下来的编译阶段是四个阶段中最为复杂的阶段,它包括词法和语法的分析, 最终生成对应硬件平台的汇编语言(不同的处理器有不同的汇编格式),具体生成什么平台 的汇编文件取决于所采用的编译器,如果用的是 GCC,那么将会生成 x86 格式的汇编文件,如果用的是针对 ARM 平台的交叉编译器,那么将会生成 ARM 格式的汇编文件。
如果想要获得C源程序经过预处理和编译之后的汇编程序,方法如下:

gcc hello.i -o hello.s -S

编译选项 -S 就可以使得 gcc 在进行完第一和第二阶段之后停下来,生成一个
默认后缀名为.s 的文本文件。

3 汇编

编译器 gcc 将会调用汇编器 as 将汇编源程序翻译成 为可重定位文件。汇编指令跟处理器直接运行的二进制指令流之间基本是一一对应的关系, 该阶段只需要将.s 文件里面的汇编翻译成指令即可。
要想得到这样的一个文件,方法如下:

gcc hello.s -o hello.o -c

只要在编译的时候加上一个编译选项-c,则会生成一个扩展名为.o 的文件, 这个文件是一个 ELF 格式的可重定位(relocatable)文件,所谓的可重定位,指的是该文件虽 然已经包含可以让处理器直接运行的指令流,但是程序中的所有的全局符号尚未定位,所谓 的全局符号,就是指函数和全局变量,函数和全局变量默认情况下是可以被外部文件引用的, 由于定义和调用可以出现在不同的文件当中,因此他们在编译的过程中需要确定其入口地 址,比如 a.c 文件里面定义了一个函数 func( ),b.c 文件里面调用了该函数,那么在完成第 三阶段汇编之后,b.o 文件里面的函数 func( )的地址将是 0,显然这是不能运行的,必须要 找到 a.c 文件里面函数 func( )的确切的入口地址,然后将 b.c 中的“全局符号”func 重新定 位为这个地址,程序才能正确运行。因此,接下来需要进行第四个阶段:链接。

4 链接

如前面所述,经过汇编之后的可重定位文件不能直接运行,因为还有两个很重要的工作 没完成,首先是重定位,其次是合并相同权限的段。
我们编译一个程序通常 都需要链接系统的标准 C 库、gcc 内置库等基本库文件。因为 Linux 下任何一个程序编译都 需要用到这些基本库的全局符号。

gcc hello.o -o hello -lc -lgcc

标准 C 库和 gcc 内置库是如此的基本,因此-lc 和-lgcc 是默认的,可以省略。

gcc hello.o -o hello

通过链接,就得到了可执行文件 hello.

实用的编译选项

选项作用示例
-o 指定输出的文件名gcc a.c -o a
-E输出预处理后的代码文件gcc a.c -o a.i -E
-S输出编译后的汇编代码文件gcc a.c -o a.s -S
-c输出链接后的可重定位文件gcc a.c -o a.o -c
-g在编译结果中加入调试信息gcc a.c -o a -g
-I 指定头文件路径gcc a.c -o a -I./include
-L 指定库文件路径gcc a.c -o a -L./lib
O指定优化等级[注 1]gcc a.c -o a -O2
-static使用静态链接[注 2]gcc a.c -o a -lxxx -static
-Wall打开所有的警告gcc a.c -o a -Wall
  • 注 1:可用的优化等级有 4 个,分别是 O0,O1,O2,O3。优化等级越高,编译速度 越慢,相对而言程序运行速度越快,调试难度越大。其中 O0 表示关闭所有的优化项目。
  • 注 2:链接库文件 xxx 时,如果系统中同时存在其对应的静态库和动态库,使用此选项 可以使得程序链接静态库,使程序编译之后不依赖于该库文件。
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值