GCC即GNU Compiler Collection,即 GNU 编译器套件。
GCC 编译器支持编译多种程序语言的编译。

无论是 C 还是 C++ 程序,其从源代码转变为可执行代码的过程,具体可分为 4 个过程,分别为预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。默认情况下,gcc 指令会一气呵成,直接将源代码历经这 4 个过程转变为可执行代码,且不会保留各个阶段产生的中间文件。
在这里插入图片描述
而如果想查看这 4 个阶段各自产生的中间文件,最简单直接的方式就是对源代码进行“分步编译”,即控制 GCC 编译器逐步对源代码进行预处理、编译、汇编以及链接操作。其中,通过为 gcc 指令添加 -E 选项,即可控制 GCC 编译器仅对源代码做预处理操作。


GCC -E选项:对源程序做预处理操作
所谓预处理操作,主要是处理那些源文件和头文件中以 # 开头的命令(比如 #include、#define、#ifdef 等),并删除程序中所有的注释 // 和 /* … */。
值得注意的是,默认情况下 gcc -E 指令只会将预处理操作的结果输出到屏幕上,并不会自动保存到某个文件。因此该指令往往会和 -o 选项连用,将结果导入到指令的文件中。
我们可以为 gcc 指令再添加一个 -C 选项,阻止 GCC 删除源文件和头文件中的注释:

gcc -E -C main.c -o main.i /* -E 只进行预处理,不做编译,汇编,链接的操作 */
cpp main.c -o main.i /* cpp 即C预处理器 c preprocessor */

Linux 系统中通常用 “.i” 作为 C 语言程序预处理后所得文件的后缀名。由此,就完成了 main.c 文件的预处理操作,并将其结果导入到了 main.i 文件中。


GCC -S选项:编译非汇编文件
编译是整个程序构建的核心部分,也是最复杂的部分之一。所谓编译,简单理解就是将预处理得到的程序代码,经过一系列的词法分析、语法分析、语义分析以及优化,加工为当前机器支持的汇编代码。
通过给 gcc 指令添加 -S(注意是大写)选项,即可令 GCC 编译器仅将指定文件加工至编译阶段,并生成对应的汇编代码文件。

gcc -S main.i /* -S 只对文件进行编译,不做汇编和链接处理 */
cc -S mian.i -o main.s /* cc 即C编译器 c compiler */

可以看到,经过执行 gcc -S 指令,其生成了一个名为 main.s 的文件,这就是经过编译的汇编代码文件。也就是说默认情况下,编译操作会自行新建一个文件名和指定文件相同、后缀名为 .s 的文件,并将编译的结果保存在该文件中。
当然如果需要的话,我们还可以为 gcc -S 指令添加 -o 选项,令 GCC 编译器将编译结果保存在我们指定的文件中。
gcc -S main.i -o test.i
gcc -S 指令操作的文件并非必须是经过预处理后得到的 .i 文件,-S 选项的功能是令 GCC 编译器将指定文件处理至编译阶段结束。这也就意味着,gcc -S 指令可以操作预处理后的 .i 文件,也可以操作源代码文件:
如果操作对象为 .i 文件,则 GCC 编译器只需编译此文件;
如果操作对象为 .c 或者 .cpp 源代码文件,则 GCC 编译器会对其进行预处理和编译这 2 步操作。
如果我们想直接得到 mian.c 文件对应的汇编文件,就可以借助 gcc -S 指令。
对于最终生成的 .s 汇编文件,感兴趣的读者可执行 cat main.s 指令查看文件中的内容。在此基础上,如果想提高文件内汇编代码的可读性,可以借助 -fverbose-asm 选项,GCC 编译器会自行为汇编代码添加必要的注释,例如:
gcc -S main.c -fverbose-asm


GCC -c选项:生成目标文件
所谓目标文件,其本质为二进制文件,但由于尚未经过链接操作,所以无法直接运行。
简单地理解,汇编其实就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。相对于编译操作,汇编过程会简单很多,它并没有复杂的语法,也没有语义,也不需要做指令优化,只需要根据汇编语句和机器指令的对照表一一翻译即可。
通过为 gcc 指令添加 -c 选项(注意是小写字母 c),即可让 GCC 编译器将指定文件加工至汇编阶段,并生成相应的目标文件。例如:

gcc -c main.s
as main.s -o main.o /* as 即 汇编器assembler,将main.s翻译成一个可重定位目标文件main.o */

可以看到,该指令生成了和 main.s 同名但后缀名为 .o 的文件,这就是经过汇编操作得到的目标文件。
当然如果必要的话,还可以为 gcc -c 指令在添加一个 -o 选项,用于将汇编操作的结果输入到指定文件中,例如:
gcc -c main.s -o test.o
得到生成目标文件之后,接下来就可以直接使用 gcc 指令继续执行链接操作,例如:

gcc main.o -o democ.exe
./democ.exe

GCC -o选项:指定输出文件
gcc -o选项用来指定输出文件,如果不使用 -o 选项,那么将采用默认的输出文件。例如默认情况下,生成的可执行文件的名字默认为 a.out。
如下是 gcc -o 指令的使用语法格式:其中,用方括号 [] 括起来的部分可以忽略。
gcc [-E|-S|-c] [infile] [-o outfile]
[infile] 表示输入文件(也即要处理的文件),它可以是源文件、汇编文件或者目标文件;[outfile] 表示输出文件(也即处理的结果),可以是预处理文件、目标文件、可执行文件等。
值得一提的是,通常情况下 [infile] 处放置一个文件,但根据实际需要也可以放置多个文件,表示有多个输入文件。

将源文件作为输入文件,将可执行文件作为输出文件,也即完整地编译整个程序:

gcc main.c func.c -o app.out
ld -o app.out main.o func.o /* 使用链接器(ld)构造可执行文件 */

将 main.c 和 func.c 两个源文件编译成一个可执行文件,其名字为 app.out。如果不使用 -o 选项,那么将生成名字为 a.out 的可执行文件。
关于可执行文件app.out的运行,是通过shell调用操作系统中的加载器(loder)函数来实现的,加载器将可执行文件中的代码和数据复制到内存中,然后将CPU的控制权转移到app.out的程序开头,app程序开始执行。
关于链接就是将可重定位目标文件以及必要的系统文件组合起来。
将源文件作为输入文件,将目标文件作为输出文件,也即只编译不链接:

gcc -c main.c -o a.o

将源文件 main.c 编译为目标文件 a.o。如果不使用 -o 选项,那么将生成名为 main.o 的目标文件。

将源文件作为输入文件,将预处理文件作为输出文件,也即只进行预处理操作:

gcc -E main.c -o demo.i

对源文件 main.c 进行预处理操作,并将结果放在 demo.i 文件中。如果不使用 -o 选项,那么将生成名为 main.i 的预处理文件。

将目标文件作为输入文件,将可执行文件作为输出文件:

gcc -c func.c main.c
gcc func.o main.o -o app.out

第一条命令只编译不链接,将生成 func.o 和 main.o 两个目标文件。第二条命令将生成的两个目标文件生成最终的可执行文件 app.out。如果不使用 -o 选项,那么将生成名字为 a.out 的可执行文件。

常用的编译选项

选项作用
-o指定输出文件名称
-E只进行预处理
-S只进行预处理、编译
-c只预处理、编译、汇编,但不链接
-D使用-D name[=definition]预定义名为name的宏,若不指定值则默认宏的内容为1
-l(小写的L)使用-l libname或者-llibname,使链接器在链接时搜索名为libname.a/libname.so(静态/动态)的库文件
-L使用-Ldir添加搜索目录,即链接器在搜索-l选项指定的库文件时,除了系统的库目录还会(优先)在-L指定的目录下搜索
-I(大写的i)使用-I dir,将目录dir添加为头文件搜索目录
-include使用-include file,等效于在被编译的源文件开头添加#include "file"
-static指定静态链接(默认是动态链接)
-O0~3开启编译器优化,-O0为不优化,-O3为最高级别的优化
-Os优化生成代码的尺寸,使能所有-O2的优化选项,除了那些让代码体积变大的
-Og优化调试体验,在保留调试信息的同时保持快速的编译,对于生成可调试代码,比-O0更合适,不会禁用调试信息。
-Wall使编译器输出所有的警告信息
-march指定目标平台的体系结构,如-march=armv4t,常用于交叉编译
-mtune指定目标平台的CPU以便GCC优化,如-mtune=arm9tdmi,常用于交叉编译

文章内容来源:

http://c.biancheng.net/

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐