gcc编译器学习

Posted jetson-xie

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gcc编译器学习相关的知识,希望对你有一定的参考价值。

gcc编译器

  1. -o [file] [origin_file] 将源代码文件编译生成file文件。如gcc -o hello hello.c,也可以直接将 gcc origin_file,那么gcc将会生成a.out文件。
  2. gcc编译器在编译一个C语言程序文件时需要经过4步:

² 将源程序文件经过预处理生成.i文件

² .i文件进行汇编生成.s文件

² .s文件编译生成.o文件

² 将各个模块的.o文件链接生成程序可执行文件

  1. gcc编译器中的几种常用的编译选项:

a) -c  编译、汇编指定的源程序,但是不进行链接

b) -S  编译指定的源文件,但是不进行汇编

c) -E  预处理指定的源程序,但是不进行汇编

d) -o  [file] [origin_file]origin_file编译生成可执行文件file

e) -I  directory 指定include包含文件的搜索目录

f) -g  生成调试信息,这些信息可以被调试器捕捉调试

  1. gcc编译器区分大小写,譬如-o选项和-O选项两者之间是不一样的,-o选项表示将origin_fie编译链接后生成file可执行文件,而-O选项表示编译生成并且进行代码优化。
  2. 汇编源程序后指的是生成.o二进制文件,编译源文件指的是生成.s文件
  3. 如果使用gcc编译器的除-o选项外生成文件,再后跟-o选项,则生成的文件为-o 选项指定文件名称。如gcc -S hello.c -o hello.s则生成指定hello.s汇编代码文件。
  4. gcc -o 选项不仅可以接收输入为源代码文件,同时还可以接收目标文件如 gcc main.c test.c -o test
  5. -I 后跟参数为指定的源程序包含代码头文件所在的位置
  6. 链接器的任务:将多个目标文件连接成为一个完整,可加载,可执行的目标文件,其输入是可重定位的目标文件。链接主要任务如下:

a) 符号解析:将目标文件内的引用符号和该符号的定义联系起来

b) 将符号定义与存储器的位置联系起来,修改对这些符号的引用。

  1. 目标文件可分为三种类型:

a) 可重定位的目标文件:这种文件中包含了二进制代码和数据,这些代码和数据已经转换成了机器指令代码和数据,但是这种目标文件还不能够立刻被执行,原因是这些指令和模块使用了别的模块中的内容,但是却还没有连接这些模块,其他模块的符号对于本模块来说是未知的,这些符号的解析需要链接器进行链接,可重定位的目标文件后面通常后缀是.o

b) 可执行目标文件:这种文件也包含二进制代码和数据,但是这些文件已经经过了链接操作,和所有模块之间已经产生了联系,在链接器的解析和链接后,所有的目标文件中的符号都已经得到了解析和重定位,因此每个符号都是已经知道的,该文件可以被直接执行。

c) 共享目标文件:这是一种特殊类型的可重定位的目标文件,可以在需要它的程序运行或者加载时,动态地加载到内存之中运行,这种文件又被称作动态库文件或者共享库文件,后缀是.so

  1. ELF格式是Linux环境下最常用的目标文件格式,在大多数情况下,无论是可执行目标文件还是重定位目标文件,都使用该种格式进行存储,ELF格式目标文件由两部分组成:ELF文件头和目标文件的段。ELF文件头的前16个字节存储该文件系统的字长和字节序,剩下的部分包含了该文件的其他信息,包括ELF文件头的大小,目标文件的类型,目标机的类型,段头部表在目标文件内的文件偏移位置等。
  2. ELF文件的段部分包含:

a) 可执行文件和重定位文件均有部分

  1. .text: 代码段,存储二进制机器指令,这些指令可以直接被执行
  2. .rodata: 只读数据端。存储程序中使用的复杂常量,例如字符串
  3. .data: 数据段,存储程序中已经明确初始化的全局数据,如果某一全局变量初始化为0或者未被初始化,则会被放置于块存储段中,C语言的局部变量保存在栈上,不保存在数据段中。
  4. .bss: 块存储段。存储未被明确初始化的全局数据,在目标文件中这个段并不占用实际的空间,而只是一个占位符,告知指定位置应该预留全局数据的空间,块存储段存在的原因是为了提高磁盘空间的利用率。

以上四个段会被实际地加载到内存中,是实际的程序段,目标文件中还有一些其他段,这些信息不会加载到内存中,在生成可执行文件时已经被删除了。

 

b) 其他不一定存在的程序段

  1. .symtab: 符号表 存储定义和引用的函数和全局变量,所有引用本模块的全局符号,以及其他模块的符号,都会在符号表中有一个登记,用于链接器查找解析和重定位的位置查找。每个重定位目标文件都需要有这样一个表。
  2. .rel.text 代码段需要重定位的信息 存储需要靠重定位操作修改位置的符号表(在源代码文件中被声明但是并没有被定义的函数)
  3. .rel.data:数据段需要重定位的信息。存储需要靠重定位操作修改位置的符号汇总,这些符号在数据段中,是一些全局变量。
  4. .debug:调试信息。存储用于调试时产生的符号表,在编译程序时通过-g才能生成这个段。
  5. .line:源程序调试时的行号映射,存储源程序中每个语句的行号,在编译程序时通过-g选项才会生成。
  6. .strtab: 字符串表, 存储.symtab符号表和.debug符号表中符号的名字,这些名字是一些字符串,并且以’’结尾。
  7. 目标文件中的符号表:每个可重定位的目标文件都会有一个符号表,在这个符号表中存储的符号可以分为三种类型:

a) 本模块中引用的其他模块的全局符号

b) 本模块中定义的全局符号

c) 本模块中定义和引用的局部符号

注意:局部符号和局部变量两者是不同的概念,局部变量仅存在在内存中,局部符号则是包括了局部静态变量和局部标号,这些内容可能存在于磁盘中的文件中。分析符号表可以采用工具readelf

  1. GNU链接器也可以实现将多个重定位目标文件链接成为一个可执行目标文件,如:ld add.o main.o -o app
  2. 链接的第一步,寻找所有参与链接的目标文件的符号,查找是否符号在所有可重定位目标文件中未被找到,若有,则输出信息给用户,若无,则进行链接生成可执行文件。如果没有问题,则进行第二步重定位操作
  3. 重定位概念:当符号解析结束后,每个符号的定义位置和大小都是确定的,重定位操作可以将这些符号链接起来,在这个过程中,链接器会将需要参与链接的目标文件进行合并,不同可重定位目标的代码段之间进行合并,不同的目标文件的数据段之间进行合并,通过这一步,所有的代码段和数据都有一个统一且唯一的地址。
  4. 重定位符号引用。由于目标文件中相同的段已经合并,因此程序中对符号的引用位置就都作废了,这时候链接器会修改这些引用符号的地址,使其指向正确的地址。
  5. 从链接方式上区分,可以将程序库分为动态库(共享库)和静态库两种

a) 静态库:在可执行程序运行前已经加入到执行码中,成为可执行程序的一部分来执行。

b) 共享库:在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。

LINUX下一般程序库采用的是ELF格式,但gcc编译器同时还支持别的文件格式的程序库。

  1. 静态库是一些目标代码的集合,Linux环境下的静态库目标文件一般以.a作为目标文件的扩展名,使用ar命令创建静态库,静态库的优点在于使用简单,但是不必再编译,节省编译时间,以最短的时间生成可执行程序,静态库比使用动态库快约1%5%,但是对于现今计算机处理速度而言,这点时间微不足道。
  2. 生成静态库可以包含两步:

a) shell中编译源文件,生成一个可重定位的目标文件

 gcc -c hello.c

b) 将目标文件后加入到静态库中

ar rcs libhello.a hello.o

其中rcs参数起着至关重要的作用,r参数表示将目标文件加入到静态库中,c参数表明若是静态库文件不存在,则创建,s参数更新静态库索引,使其包含目标文件中的内容

  1. 静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,拓展名为.a
  2. 静态库创建完成后,为了使程序能够正确使用该库中的符号,因此需要制作一个包含该静态库中全局符号声明的头文件,这个头文件可以包含在应用程序的头文件中,这样就可以使用静态库了。使用静态库有以下步骤:

a) 制作头文件对该静态库中的函数进行生命,并将在需要使用到该静态库中函数的源码中包含该头文件。

b) 使用gcc-l选项来指定静态库或者使用-L选项来制定库文件的搜索路径,-l-L之后都是直接带参数不加空格,如:

gcc -L. main.c -ltest -o hello

在使用gcc链接器进行静态库链接时,需要注意参数所处的位置

-l是链接器选项,一定要放在编译的源文件名称的后面,也就是放在main.c之后,若放在前面则会报错。gcc也支持-static选项对静态库进行链接。

gcc main.c -static ./libtest.a -o hello

  1. 动态库在工程中较经常使用,Windows环境底下的DLL文件和Linux环境底下的so文件,动态库是在程序启动时被装载的,当一个应用程序使用该静态库时,其他应用程序也依然可以使用这个静态库,这个被多进程同时使用的动态库在内存中只有一个副本。
  2. 动态库优点:

a) 是代码,相比于二进制文件更好更新和管理

b) 便于程序的发布

c) 易于程序模块的更新,更新并不影响程序使用旧的,非向后兼容的版本。

d) 在执行特定程序时,可以覆盖整个库或更新库中的特定函数。

e) 更新操作不会影响正在运行的程序,他们仍然会使用已经装载的库

  1. Linux环境下使用gcc创建动态库,因为动态库可以被多个进程共享加载,因此需要生成和位置无关的目标文件,这时需要使用gcc编译器的-fPIC选项,该选项可以生成与位置无关的代码,同时还需要使用-shared 选项,该选项将与位置无关的代码制作成为动态库

gcc -fPIC -shared -o [动态库文件的名称] [源码名称]

其中[动态库文件的名称]选项中是以.so结尾的即将生成或已经存在的动态库名,[源码名称]是指源码所在位置的文件名称
26. 动态库的使用:动态库与静态库一样,需要制作一个包含该动态文件中全局符号声明的头文件,这个头文件应包含在源码中。使用时如下:

gcc main.c ./test.so -o app

同时也可以使用-l选项来链接动态库,对动态库文件名有要求。

  1. gcc工具:

a) Binutils:一组可以用于编译、链接、汇编和其他调试目的的程序,包括arasld

b) gcc:GNU编译器,Linux环境下默认C语言编译器

c) Glibc:GNUC语言标准库。

以上是关于gcc编译器学习的主要内容,如果未能解决你的问题,请参考以下文章

arm-linux-gnueabi-gcc 和 arm-linux-gnueabihf-gcc的区别 ——ARM交叉编译器的学习(学习记录)

想学习Linux,gcc编译器怎么安装?

[An Introduction to GCC 学习笔记] 01 介绍GCC发展历史

gcc,一个神奇的编译器

快速了解Linux上gcc编译器

如何在 gcc 中禁用编译器优化?