文章目录
一、gcc编译器背后的故事
1. gcc不是一个人在战斗
gcc不是一个人在战斗,gcc背后其实有一堆战友。
gcc(GNU C Compiler)是编译工具,将C/C++编写的程序转化为处理器能够执行的二进制代码的过程。
gcc中Binutils是一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。这一组工具是开发和调试不可缺少的工具,分别简介如下。
addr2line:用来将程序地址转换成其他对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
as:主要用于汇编。
ld:主要用于连接。
ar:主要用于创建静态库。
ldd:可以用于查看一个可执行程序依赖的共享库。
objcopy:将一种对象文件翻译成另一种格式,譬如将.bin转化成.elf、或者将.elf转化成.bin等。
objdump:主要的作用是反汇编。
readelf:显示有关ELF文件的信息。
size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等。
2. ELF文件分析
ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。一个典型的ELF文件包含下面几个段。

text:已编译程序的指令代码段。
rodata:ro代表read only,即只读数据。
data:已初始化的C程序全局变量和静态局部变量。
bss:未初始化的C程序全局变量和静态局部变量。
debug:调试符号表,调试器用此段的信息帮助调试。
二、Linux GCC常用命令
1. 简介
GCC的意思也只是GUN C Compiler而已。 经过了这么多年的发展,GCC已经不仅仅能支持C语言,它现在还支持Ada语言、C++语言、Java语言、Objective语言、Pascal语言、COBOL语言,以及支持函数式编程和逻辑的Mercury语言等等。而GCC已经不单是GNU C语言编译器的意思了,而是变成了GNU Compiler Collection,也是GNU编译器家族的意思。另一方面,说到GCC对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
2. 简单编译
示例程序如下
test.c
#include<stdio.h>
int main(void)
{
printf{"Hello World!\n"};
return 0;
}
这个程序,一步到位的编译指令是:
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经预处理之后的代码。打开test.i文件,看一看,就明白了。后面那条指令,是直接在命令行窗口输出预处理后的代码。
gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将stdio.h文件中的内容插入到test.c中了。
2.2 编译为汇编代码(Compilation)
预处理之后,可直接对生成的test.i文件编译,生成汇编代码:
gcc -S test.i -o test.s

gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。
2.3 汇编(Assembly)
对于2.2生成的汇编代码文件test.s,gcc汇编器负责将其编译为目标文件,如下:
gcc -c test.s -o test.o
结果遇到如下错误

经过多番排查,最终发现是在编译为汇编代码时"gcc -s test.i -o test.s"中的"-s"应该大写的S,修改后就能成功编译

2.4 连接(Linking)
gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于2.3生成的.o文件test.o,将其与C标准输入输出库进行连接,最终生成程序test。
gcc test.o -o test
然后在命令行窗口中用./执行test
./test

3. 多个程序文件的编译
通常整个程序是由多个源文件组成的,相应的也就形成了多个编译单元,使用gcc能够很好地管理这些编译单元。假设有一个由test1.c和test.2两个源文件组成的程序,可使用如下命令将它们编译为可执行文件。
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. 库文件连接
开发软件时,完全不使用第三方函数库的情况是很少见的 ,通常来讲都需要接著许多函数库的支持才能够完成相应的功能。
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选项,强制使用静态库。
gcc -L /usr/dev/mysql/lib -static -lmysqlclient test.o -o test
静态库链接时搜索路径顺序:
1.ld会去找GCC命令中的参数-L
2.再找gcc的环境变量LIBRARY_ PATH
3.再找内定目录/lib /usrib /ustrlocalib这是当初compile gcc时写在程序内的
动态链接时、执行时搜索路径顺序:
1.编译目标代码时指定的动态库搜索路径
2.环境变量LD_ LIBRARY PATH 指定的动态库搜索路径
3.配置文件/etcld.so.conf中指定的动态库搜索路径
4.默认的动态库搜索路径/lib
5.默认的动态库搜索路径/usr/lib
三、总结
通过以上实操,可以知道在默认情况下,gcc在链接时优先使用动态库。Linux下在命令行窗口输入命令和gcc编译程序的时候要注意区分大小写,不然会造成不必要的错误,导致大量消耗时间。
四、参考文献
https://blog.csdn.net/weixin_44941716/article/details/118340186?spm=1001.2014.3001.5501
953

被折叠的 条评论
为什么被折叠?



