GCC 编译全过程

GCC 编译全过程

本文仅总结了大部分常用的 GCC 命令,共大家学习参考

GCC 简介

GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。

简单编译

实例程序 main.c 如下:

#include <stdio.h>

int main(void)
{
	printf("Hello ppqppl! \n");
	return 0;
}

这个程序,一步到位的编译命令如下:

gcc main.c -o main

实际上,上述的编译步骤分为四个阶段:

1.预处理(也叫预编译,Preprocessing)

2.编译(Compilation)

3.汇编(Assenbly)

4.连接(Linking)

预处理

主要包括一下过程:

1.将所有的 #define 删除,并展开所有宏定义,处理所有的条件预编译指令,例如 #if #ifdef 等

2.处理 #include 预编译命令,将被包含的文件插入到该预编译指令的位置

3.删除所有注释

4.添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告的行号

5.保留所有的 #pragma 编译器指令,后迅速编译过程需要使用

预处理命令如下:

gcc -E main.c -o main.i
#或
gcc -E main.c

可以直接输出预处理后存放在 main.c 中的代码,gcc 的 -E 选项,可以让编译器在预处理后停止,-o 表示输出编译后的文件。并输出预处理结果。

编译为汇编代码

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码

GCC 命令如下:

 gcc -S main.i -o main.s

gcc 的 -S 选项,表示在程序编译期间,在生成汇编代码后,停止

汇编

汇编过程调用对汇编代码进行处理,生成机器可以识别的指令,由于每一个汇编语句几乎都对应一条处理器指令,所以,汇编想对于编译过程简单一些,调用 Binutils 中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可

当程序由多个源代码文件构成时,每个文件都要先完成汇编,生成 .o 目标文件之后,才能进行链接

对于上一小节中生成的汇编代码文件 main.s,gas 汇编器负责将其编译为目标文件

gcc -c main.s -o main.o
# 或者直接使用 as 进行汇编
as -c main.s -o main.o

注意: hello.o目标文件为 ELF( ELF ) 格式的可重定向文件
关于 ELF 文件的介绍,请看:GCC 一步到位的 ELF 部分

连接

链接分为:动态链接、静态链接

静态链接

在编译阶段直接把静态库加入到可执行文件中,可执行文件较大。连接器将函数代码从所在地拷贝到最终执行程序中

动态链接

链接阶段,仅仅只加入一些描述信息,而程序执行是从系统中制定位置把相应动态库加载到内存中

注意: 关于动态库与静态库的进一步内容,可以看我的另一篇文章:GCC 入门之 静态库 与 动态库

连接过程:

为创建可执行文件,连接器必须要完成的任务是:符号解析、重定位

符号解析:把目标文件中符号的定义和引用联系起来

重定位:把符号定义和内存地址对应起来然后修改所有对符号的作用

gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。

对于上一小节中生成的 main.o,将其与C标准输入输出库进行连接,最终生成程序 main

gcc main.o -o main

在命令行窗口,可以直接执行 ./test,运行结果如下:

image

多个程序文件的编译

通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理这些编译单元。假设有一个由 main.c 和 sub1.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 main,可以使用下面这条命令:

gcc main.c sub1.c -o main

如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

gcc -c main.c -o main.o
gcc -c sub1.c -o sub1.o
gcc main.o sub1.o -o main

检错

gcc -pedantic main.c -o main

-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助 Linux 程序员离这个目标越来越近。

或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。

除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以 -W 开头,其中最有价值的当数 -Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。

gcc -Wall main.c -o main

GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上 -Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:

gcc -Werror main.c -o main
posted @ 2022-09-22 00:27  ppqppl  阅读(387)  评论(0编辑  收藏  举报