详细学习GCC
一、GCC编辑器背后的故事
1.GCC编译工具集中各部分的作用
GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理C语言。GCC 很快地扩展,变得可处理 C++。后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective-C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。
gcc命令下各选项的含义:
- -E:仅作预处理,不进行编译、汇编和链接
- -S:仅编译到汇编语言,不进行汇编和链接
- -c:编译、汇编到目标代码(也就是计算机可识别的二进制)
- -o:执行命令后文件的命名
- -g:生成调试信息
- -w:不生成任何警告
- -Wall:生成所有的警告
2.Binutils
一组二进制程序处理工具,包括:addr2line、ar、 objcopy、objdump、as、ld、ldd、readelf、 size等。这一组工具是开发和调试不可缺少的工具,分别简介如下:
-
addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
-
as:主要用于汇编
-
ld:主要用于链接
-
ar:主要用于创建静态库,静态库在之后进行了详细解释在此不再赘述
-
ldd:可以用于查看一个可执行程序依赖的共享库
-
objcopy:将一种对象文件翻译成另一种格式
-
objdump:主要作用是反汇编
-
readelf:显示有关ELF文件的信息
-
size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等
二、GCC 的简单编译
1.GCC编译过程
gcc的编译分成四个步骤
- 预处理
- 编译
- 汇编
- 链接
首先生成一个简单的test.c代码
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
(1)预处理
预处理命令源文件test.c文件预处理成test.i,其中-E选项使GCC在进行完预处理后即停止
gcc -E test.c -o test.i
查看test.i内容:
vim test.i
(2)编译
编译过程是对预处理完的文件进行一系列的词法分析,语法分析,词义分析及优化后生成相应的汇编代码,代码将预处理生成的test.i文件编译成汇编程序test.s,-S选项使GCC在执行完编译后停止,生成汇编程序
gcc -S test.i -o test.s
查看test.s中的内容:
vim test.s
(3)汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀.o的目标文件中。
gcc -c test.s -o test.o
或者直接调用as进行汇编
as -c test.s -o test.o
(4)链接
链接分为静态链接和动态链接,下面详细讲了静态库和动态库的使用这里不再赘述
链接动态库:
gcc test.c -o test
./test
size test
链接静态库:
gcc -static test.c -o test
./test
size test//查看大小
可以看出静态库相比动态库text的代码尺寸变得极大
2.分析ELF文件
1.ELF文件的段
一个典型的ELF文件包含下面几段:
text:已编译程序的指令段代码
rodata:ro代表read only,即只读数据
data:已初始化的C程序全局变量和静态局部变量
bss:未初始化的C程序全局变量和静态局部变量
debug:调试符号表,调试器用此段的信息帮助调试
我们可以使用readelf -S查看各个section的信息如下:
readelf -S test
2.反汇编ELF
由于ELF文件无法被当做普通文本文件打开,如果希望直接查看一个ELF文件包含的指令和数据没需要使用反汇编的方法。
使用objdump -D
对其进行反汇编
objdump -D test
二、用GCC生成静态库.a和动态库.so
1.静态库和动态库
库是一段编译好的二进制代码,加上头文件就可以供别人使用。
库的使用情况分为两种:一种情况是某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件;另外一种情况是,对于某些不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要 Link 一下,不会浪费编译时间。
上面提到库在使用的时候需要 Link,Link 的方式有两种,静态和动态,于是便产生了静态库和动态库。
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码汇中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在。
下面我们举例说明Linux中如何创建静态库和动态库并使用它们。
2.用GCC 生成静态库.a
(1)编译生成例子程序hello.h,hello.c和main.c
hello.h:
hello.c:
main.c:
(2)生成并使用静态库
生成并使用静态库包含三个步骤:
- 第一步:生成.o目标文件
静态库和动态库都是由.o目标文件创建的,因此我们首先将源程序通过gcc编译成.o文件,并利用ls命令查看已经生成了hello.o文件
- 第二步:由.o文件创建静态库
静态库文件名的命名规范以lib为前缀,后接静态库名,扩展名为.a。下面程序中,创建的静态库名为myhello,则静态库名为libmyhello.a。接下来我们使用ar命令创建静态库文件,并使用ls命令查看已生成libmyhello.a文件
- 第三步:在程序中使用静态库
制作完静态库之后使用它内部的函数只需要在公用函数的源程序中包含公用函数的原型声明,然后用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。下面我们生成目标程序hello,一共有三种方法:
- 方法一:gcc -o hello main.c -L. -lmyhello
- 方法二:gcc main.c libmyhello.a -o hello
- 方法三:生成main.o之后再生成可执行文件
生成动态库同样分成以下三个步骤
- 第一步:生成,o目标文件
- 第二步:由.o文件创建动态库文件
动态库的命名规则和静态库命名规则类似,也是在动态库名前加前缀lib,但其文件扩展名为.so。
- 第三步:在程序中使用动态库:
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在gcc命令生成目标文件时指明动态库名进行编译。
在这里我们出现了报错,原因是找不到动态库文件libmyhello.so,在程序运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件,若找到,则载入动态库,否则将提示类似上述错误而终值程序运行。我们将文件libmyhello.so复制到目录/usr/lib中:
任然出现了报错,经过查阅资料是因为移动该文件没有权限,解决方法为使用sudo命令,其可以暂时性的提升等级去移动我的文件到系统目录下。
这样动态库便生成成功了。
4.静态库和动态库的优先级
使用静态库和动态库编译成目标程序使用的gcc命令是完全一样的,接下来我们讨论当静态库和动态库同名时,gcc命令会使用哪一个库文件呢。
首先删除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态:
在这里我们同样遇到了前边的没有权限的问题,同样使用sudo 命令即可解决
接下来创建静态库文件libmyhello.a和动态库文件libmyhello.so
通过ls命令可知我们已经生成了libmyhello.a和libmyhello.so,然后使用函数库生成目标文件并运行程序发现报错,可见当静态库和动态库同名时,gcc会有限使用动态库默认去连/usr/lib和/lib等目录中的动态库。
5.多个程序文件的编译
(1)静态库的使用
我们创建A1.c、A2.c、A.h和test.c并生成目标文件:
gcc -c A1.c A2.c
其中A1.c
A2.c
A.h
test.c
生成静态库.a文件
ar crv libafile.a A1.O A2.O
使用.a库文件创建可执行程序
gcc -o test test.c libafile.a
./test
(2)动态库的使用
生成目标文件
gcc -c -fpic A1.c A2.c
生成共享库.so文件
gcc -shared *.o -o libsofile.so
使用.so库文件,创建可执行文件
gcc -o test test.c libsofile.so
./test
出现错误,运行ldd test,查看链接情况
ldd test
可见确实找不到.so文件,同样将.so文件复制到/uer/lib路径下
sudo cp libsofile.so /usr/lib
./test
三、实作
编写x2x和x2y函数,main函数调用x2x.c和x2y.c,使用gcc分别生成静态库和动态库进行链接,并比较二者的大小
x2x.c:
x2x.h:
x2y.c:
x2y.h:
main.c:
生成三个目标文件.o
gcc -c x2x.c x2y.c main.c
使用ar工具生成静态库
ar -cr libfile.a x2x.o x2y.o
将main.c的目标文件与静态库相链接,并编译得到结果,计算文件大小
gcc -o x main.c -L. -lfile
./main
size main
生成动态库的目标文件
gcc -c -fPIC x2x.c x2y.c
生成动态库libfiel.so
gcc -shared *.o -o libfiel.so
将main.c与动态库相链接,并执行代码,根据报错需要将libfile.so文件移动到/usr/lib,之后编译得到结果,计算文件大小
gcc -o main main.c libfile.so
./main
sudo cp libfile.so /usr/lib
./main
size main