GCC(GNU Compiler Collection)是Linux世界里最重要的编译工具,几乎所有开源软件都是用GCC编译出来的。它最早只能编译C语言,现在已经支持C++、Objective-C、Fortran、Ada、Go等十几种语言。很多刚接触Linux开发的同学对GCC的编译流程一知半解,只知道敲个gcc main.c -o main,中间发生了什么完全不明白。今天就把GCC的完整构建流程拆解开来说。
一个C源文件从你写完到变成可执行文件,中间要经过四个步骤:预处理、编译、汇编、链接。GCC可以一条命令搞定全部四个步骤,也可以加参数把每个步骤拆开来观察。这四个步骤的产物分别是.i(预处理后的源码)、.s(汇编代码)、.o(目标文件)、最终的可执行文件。
第一步是预处理。GCC -E参数可以只跑预处理,输出结果到.i文件。预处理做什么呢?把#include包含的头文件内容全部展开(相当于把.h文件的内容直接复制到.c文件里)、处理#define宏替换、去掉//和/* */注释、处理#ifdef条件编译。比如你在代码里写了#define PI 3.1415926,预处理之后所有PI都被替换成了3.1415926。用-E参数能看到展开后的完整代码,排查宏展开相关的bug特别有用。
第二步是编译。GCC -S参数把预处理后的代码编译成汇编代码(.s文件)。这个阶段做的事情是语法分析、词法分析、语义分析、中间代码生成、优化、目标代码生成。编译是整个流程里最核心也最耗时的一步,GCC的各种优化选项(-O0、-O1、-O2、-O3)就是在这里生效的。汇编代码的人类可读性还不错,想知道一段C代码在CPU上变成了什么指令,加-S参数就行。
第三步是汇编。GCC -c参数把.s汇编文件变成.o目标文件。这一步用的是as汇编器,把汇编指令翻译成机器码。目标文件里已经包含了CPU能直接执行的机器指令,但这还不是完整的可执行文件——它可能引用了其他模块里的符号(比如printf),自己也还没分配确切的内存地址。
第四步是链接。这一步用ld链接器,把一个或多个.o目标文件以及需要的库文件(静态库.a或动态库.so)链接在一起,生成最终的可执行文件。链接分静态链接和动态链接:静态链接把库的代码直接复制到可执行文件里,文件体积大但运行时不依赖外部库;动态链接只记录库的位置,运行时由动态链接器加载到内存,文件小但依赖运行环境里有对应的库。GCC默认是动态链接,-static参数可以指定静态链接。
最常用的GCC参数有哪些?-o 指定输出文件名;-Wall 打开警告信息(强烈建议开,很多隐藏bug会被警告提示出来);-O2 或 -O3 开启优化;-g 生成调试信息(配合gdb调试用);-I 指定头文件搜索路径;-L 指定库文件搜索路径;-l 指定要链接的库(比如-lpthread链接线程库);-std=c99 或 -std=c11 指定C语言标准版本;-D 命令行定义宏(比如-DDEBUG)。
多文件项目怎么编译?最简单的方式是gcc main.c func.c util.c -o myprog,一次把所有源文件都编译了。但文件多了之后每次全量编译太慢,而且改一个文件重编全部也没效率。这时候就要用Makefile——把编译规则写在Makefile里,make工具会比较源文件和目标文件的时间戳,只重编修改过的文件。GCC配合Makefile是Linux C开发的标准工作流。
交叉编译是GCC的强项。用gcc编译出来的程序只能在同架构的机器上跑(比如x86上编的程序不能直接在ARM开发板上跑),但GCC支持交叉编译——在一台机器上编出另一台机器(比如ARM、MIPS)能执行的程序。嵌入式开发几乎都用交叉编译,因为目标平台资源有限装不了完整的编译环境。交叉编译需要安装对应的交叉编译器,比如arm-linux-gnueabihf-gcc。
常见的GCC报错有哪些?"undefined reference"错误是链接阶段找不到符号,通常是忘记链接某个库或者函数名拼写错了。"No such file or directory"是编译阶段找不到头文件或源文件,检查include路径和-I参数。隐式声明警告说明代码里调用了没有声明的函数,通常是忘记include对应的头文件。这些错误信息看懂了,排查起来很快。
最后提一个小技巧:用-pg参数编译程序,运行后会生成gmon.out文件,配合gprof工具可以分析程序的性能瓶颈——哪个函数被调用了多少次、占用了多少CPU时间。这是性能分析的入门工具,虽然简单但非常实用。GCC的强大之处在于它不仅能编译,还提供了丰富的工具链辅助开发调试,把这些用熟练了,C/C++开发的效率会提高很多。
上一篇:100M宽带下载只有6MB/s?是被限速了还是哪里出了问题
下一篇:没有了