文章目录
前言
GCC(GNU C Compiler)是编译工具。本文所要介绍的将 C/C++语言编写的程序 转换成为处理器能够执行的二进制代码的过程即由编译器完成。
一、GCC编译工具集
1、工具
工具 | 说明及作用 |
---|---|
ar | 汇编器,将汇编语言代码装换成目标文件 |
as | 汇编 |
ld | 链接器,把编译生成的多个目标文件组织成最终的可执行程序文件 |
nm | 可用于查看目标文件中出现的符号 |
objcopy | 可用于目标文件格式转换,如.bin 转换成 .elf 、.elf 转换成 .bin等 |
objdump | 可用于查看目标文件的信息,最主要的作用是反汇编 |
readelf | 可用于查看目标文件或可执行程序文件的信息 |
size | 可用于查看目标文件不同部分的尺寸和总尺寸 |
2、系列参数
参数 | 作用 |
---|---|
-E | 只做预处理,不编译 |
-S | 只编译,将C程序编译为汇编文件 |
-c | 只汇编,不链接 |
-o | 指定输出的文件名 |
二、Linux GCC 常用命令
1、简介
GCC 的意思也只是 GNU C Compiler而已。经过了这么多年的发展,GCC已经不仅仅能支持 C 语言;它现在还支持Ada语言、C++ 语言、Java 语言、Objective C语言、Pascal语言、COBOL语言,以及支持函数式编程和逻辑编程的Mercury语言等。而GCC也不再单只是 GNU C语言编译器的意思了,而是变成了GNU Compiler Collection 也即是 GNU编译器家族的意思了。
2、简单编译
先创建新目录mkdir test1.2
,然后输入命令cd test1.2
进入新目录
test.c
一步到位的编译指令gcc test.c -o test
实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)。
2.1 预处理
gcc -E test.c -o test.i
或 gcc -E test.c
可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。预处理结果就是将 stdio.h 文件中的内容插入到 test.c 中。
2.2 编译为汇编代码(Compilation)
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:
gcc -S test.i -o test.s
gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
2.3 汇编(Assembly)
对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件,如下: gcc -c test.s -o test.o
2.4 连接(Linking)
gcc 连接器负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序 test。
gcc test.o -o test
执行./test
3、多个程序文件的编译
使用 GCC 能够很好地管理多个源文件对应形成的编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用如下命令: gcc test1.c test2.c -o test
如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test
4、检错
gcc -pedantic illcode.c -o illcode
-pedantic 选项能够帮助程序员发现部分不符合 ANSI/ISO C标准的代码,事实上只有 ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。
除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W 开头,其中使用-Wall能够使GCC 产生尽可能多的警告信息。
gcc -Wall illcode.c -o illcode
GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。但应该尽量避免产生警告信息,所以在编译程序时带上-Werror 选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werror test.c -o test
5、库文件连接
开发软件时,几乎都需要借助许多函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(so、或 lib、dll)的集合。
虽然 Linux 下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下;Windows所使用的库文件主要放在 Visual Stido的目录下的 include 和lib,以及系统文件夹下。但也有的时候,我们要用的库不再这些目录下,所以 GCC 在编译时必须用自己 的办法来查找所需要的头文件和库文件。
其中 inclulde 文件夹的路径是/usr/dev/mysql/include,lib 文件夹是/usr/dev/mysql/lib
5.1 编译成可执行文件
首先要进行编译 test.c 为目标文件,这个时候需要执行
gcc –c –I /usr/dev/mysql/include test.c –o test.o
5.2 链接
最后把所有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test
Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的
5.3 强制链接时使用静态链接库
默认情况下,GCC 在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。 在/usr/dev/mysql/lib 目录下有链接时所需要的库文件 libmysqlclient.so 和libmysqlclient.a,为了让 GCC 在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
静态库链接时搜索路径顺序:
- ld 会去找 GCC 命令中的参数-L
- 再找 gcc 的环境变量 LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的
动态链接时、执行时搜索路径顺序:
- 编译目标代码时指定的动态库搜索路径
- 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径
- 配置文件/etc/ld.so.conf 中指定的动态库搜索路径
- 默认的动态库搜索路径/lib
- 默认的动态库搜索路径/usr/lib
三、C运行库
C语言标准主要由两部分组成:一部分描述 C 的语法,另一部分描述C标准库。 C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义,譬如常见的 printf 函数便是一个C 标准库函数,其原型定义在stdio头文件中。C 语言标准仅仅定义了 C 标准库函数原型,并没有提供实现。因此,C 语言编译 器通常需要一个 C 运行时库(C Run Time Libray,CRT)的支持。C 运行时库又常简称为 C运行库。与C语言类似,C++也定义了自己的标准,同时提供相关支 持库,称为 C++运行时库。
1、准备工作
由于 GCC 工具链主要是在 Linux 环境中进行使用,因此本文也将以Linux系统作为工作环境。为了能够演示编译的整个过程,先创建一个工作目录 test0,然后用文本编辑器生成一个C语言编写的简单 Hello.c 程序为示例,其源代码如下所示:
2、编译过程
2.1预处理
使用 gcc 进行预处理的命令如下:
gcc -E hello.c -o hello.i
将源文件 hello.c 文件预处理生成 hello.i
GCC 的选项-E 使 GCC 在进行完预处理后即停止
预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
2.2编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及 优化后生成相应的汇编代码。 使用 gcc 进行编译的命令如下:
gcc -S hello.i -o hello.s
将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
GCC 的选项-S 使 GCC在执行完编译后停止,生成汇编程序
2.3汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。注意:目标文件已经是最终程序的某一部 分了,但是在链接之前还不能执行。
使用 gcc 进行汇编的命令如下:
gcc -c hello.s -o hello.o
将编译生成的 hello.s 文件汇编生成目标文件 hello.o
GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标 文件后,才能进入下一步的链接工作。
或者直接调用 as 进行汇编 as -c hello.s -o hello.o
使用 Binutils 中的 as 将 hello.s 文件汇编生成目标文件 注意:hello.o 目标文件为 ELF(Executable and Linkable Format)格式的可重定向文件。
2.4连接
如果使用命令“gcc hello.c -o hello”则会使用动态库进行链接,生成的ELF可执行文件的大小(使用Binutils的size命令查看)和链接的动态库(使用 Binutils 的 ldd 命令查看)如下所示:
gcc hello.c -o hello
size hello //使用 size 查看大小
ldd hello //可以看出该可执行文件链接了很多其他动态库,主要是 Linux 的 glibc 动态库
3、ELF文件
3.1说明
ELF文件(Executable Linkable Format)是一种文件存储格式。ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。
3.2分类
在使用方面,ELF文件主要分为三类:
可执行文件(.out):Executable File,包含代码和数据,是可以直接运行的程序。其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行。
可重定位文件(.o文件):Relocatable File,包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。
共享目标文件(.so):Shared Object File,也称动态库文件,包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。
3.3ELF文件的段
一个典型的 ELF 文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro 代表 read only,即只读数据(譬如常数 const)。 .data:已初始化的 C 程序全局变量和静态局部变量。
.bss:未初始化的 C 程序全局变量和静态局部变量。 .debug:调试符号表,调试器用此段的信息帮助调试。
3.4文件格式
ELF文件的作用有两个,一是用于程序链接(为了生成程序);二是用于程序执行(为了运行程序)。从链接和运行的角度,可以将 ELF 文件的组成部分划分为链接视图和运行试图 这两种格式。elf文件是有一定的格式的,从文件的格式上来说,分为汇编器的链接视角与程序的执行视角两种去分析ELF文件。从程序执行视角来说,这就是Linux加载器加载的各种Segment的集合。比如只读代码段、数据的读写段、符号段等等。而从链接的视角上来看,elf又分为各种的sections。
查看各个section的信息
3.5反汇编ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包 含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编如下:
objdump -D hello
使用 objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:
gcc -o hello -g hello.c //要加上-g 选项
objdump -S hello
四、总结
通过查询资料和使用gcc编译器,对其部分工具和整个流程有了一定的了解。gcc作为一个基础性的编辑器,其运行十分方便,对于不同调用要求也有一定的功能适用。
五、参考文献
于渊.Orange‘s:一个操作系统的实现.北京:电子工业出版社,2009.6:123-126
Linux GCC 常用命令
https://cloud.tencent.com/developer/article/1710868#1.1
http://t.csdn.cn/BU6ZG