GCC合集,后续介绍g++、gdb、静态库和动态库以及makefile、cmake
文章目录
前言
介绍GCC(GNU Compiler Collection)编译器的编译过程。
一、GCC编译器
GCC 是 Linux 下的编译工具集,是「GNU Compiler Collection」的缩写,包含 gcc、g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar、nm 等。
GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等语言均能进行编译。GCC 还可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等软件平台。
1.1安装
$ sudo apt-get update
$ sudo apt-get install gcc g++
$ sudo gcc --version
$ sudo g++ --version
1.2 GCC 编译过程
创建一个hello.c文件为实例
#include <stdio.h>
#define Max 3
int main()
{
int i;
for(i=1; i<Max; i++)
{
printf("Hello world\n"); // output:"Hello world"
}
return 0;
}
在命令行中可以输入以下指令实现编译test.c,并运行
$ gcc hello.c -o hello
$./hello
$ mkdir ./test
$ gcc hello.c -o ./test/hello #修改路径
-o:output 指定文件名及路径,默认输出当前路径
GCC编译器在对程序进行编译的时候,分为四个步骤:
预处理(Pre-Processing):
- 作用:展开头文件,宏替换,去掉注释行
- 结果:C程序,通常是以.i作为文件扩展名
- 方法:使用参数 -E 生成预处理后的 C 文件 必须使用 -o 将名字命名为 hello.i 命令如下
$ gcc -E test.c -o test.i -v
查看运行结果:(cc1)
注意:虽然cc1主要作用是将源文件生成汇编代码,但在预处理中,我们主要处理的是头文件、宏定义、注释。然而我们在下一步的编译过程依然会使用 cc1
查看代码(只展示了后面):
查看 main() 中的内容,宏定义的Max替换为了3 注释//output: Hello World 也被去掉了
编译(Compiling):
- 作用:gcc 首先检查代码的规范性、语法错误等,并将代码编译成汇编语言。
- 结果:以 .s 作为文件扩展名的汇编文件。
- 方法:使用参数 -S 将 hello.i 转换成 hello.s
查看运行结果:(cc1)$ gcc -S hello.i $ gcc -S hello.i -o hello.s -v
汇编(Assembling):
-
作用:把编译阶段生成的 .s 文件转化成目标文件
-
结果:以 .o 结尾的二进制文件(.o文件,object)
-
方法:使用参数 -c 编译汇编文件为二进制文件
$ gcc -c hello.s $ gcc -c hello.s -o hello.o
查看运行结果:as
-
链接(Linking):
- 静态链接:编译过程中,将静态库加入到可执行文件中
- 动态链接:程序执行时,从系统中将相应的动态库加到内存中去
-
Linux系统中,gcc编译链接时动态库搜索路径的顺序通常为:
1)从gcc命令参数-L指定路径寻找;
2)再从环境变量LIBRARY_PATH指定的路径寻址;
3)再从默认路径/lib、/usr/lib、/usr/local/lib寻找。 -
Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:
1)先搜索编译目标代码时指定的动态库搜索路径;
2)再从环境变量LD_LIBRARY_PATH指定的路径寻址;
3)再从配置文件/etc/ld.so,sonf中指定的动态库搜索路径;
4)再从默认路径/lib、/usr/lib寻找。 -
Linux系统下,可用ldd命令查看一个可执行程序依赖的动态库。
-
作用:调用链接器将所有目标文件以及对程序需要调用的库进行链接,得到可执行文件
-
结果:以 .exe(.out) 作为文件扩展名的可执行文件
-
方法:
$ gcc hello.o -o hello -v #链接动态库 $ size hello $ ldd hello # 动态库的相关
查看运行结果:
这里再来看看,ELF可执行文件的大小以及链接的动态库:
在主程序中调用了printf(),stdio.h中只有函数声明。并没有定义函数实现。系统把函数实现都做到了名为 libc.so.6 的库文件中。gcc在链接时会搜索 /usr/lib64 下查找,并链接到 libc.so.6 库函数中,这样就有 printf 的实现了。
注意:链接动态库和静态库有可能重合,所以,当有同名的静态库和动态库文件时,
gcc链接默认优先选择动态库,若要让链接选择静态库则需指定-static,强制性使用静态库链接。
使用以下代码,可以看到相对动态链接ELF文件大了许多:
$ gcc -static hello.c -o hello # 链接静态库
$ size hello # 文件会变的很大
$ ldd hello # 说明没有动态库
扩展:ELF文件
简介:ELF(Executable Linkable Format)是可执行与可链接格式文件
作用:存储可执行文件、目标代码、共享库和核心转储文件
应用:Unix系统(Linux)和类Unix系统(FreeBSD)中
组成部分
- ELF头部(ELF Header):描述文件总体布局,包括文件类型、目标框架、入口地址等信息。
查看方法:
$ readelf -h hello
文心一言:(了解)
其中包含了关于名为“hello”的程序的信息。这些信息包括了程序的文件头、版本、类型和机器类型等。此外,还提到了该程序是用C语言编写的,并且是一个共享对象文件(.so)。程序使用的是X86-64架构的处理器。在输出中可以看到一些其他的信息,比如入口地址、开始的程序头部大小以及程序头部的大小等。
- 程序头部表(Program Header Table):描述文件中各个段(segment)的信息。
$ readelf -l hello
文心一言:(了解)
该程序用于查看和分析ELF文件的信息。它显示了ELF文件类型为DYN(共享对象文件),并且有一个名为“hello”的入口点地址为0x1060。有13个程序头信息,从偏移量64开始。这些程序头信息包括类型、偏移量、虚拟地址、物理地址、段描述符表大小、文件大小、可执行文件大小、交换文件大小、运行时大小、装载大小、动态大小、GNU_PROPERTY、GNU_STACK和GNU_EH_FRAME等字段。
- 段表(Section Header Table):描述文件中各个段(section)的详细信息。
$ readelf -S hello
列出每个节区的名称、类型、大小、地址等信息
略
- 段(section):实现存储代码、数据、符号表、重定位信息和调试信息等内容。ELF的核心部分,包含程序运行需要的所有文件
ELF文件中的段
一个典型的ELF文件包含下面几个段:
- .text:已编译程序的指令代码段。
- .rodata:ro表示read only,即只读数据。
- .data:已初始化的C程序全局变量和静态局部变量。
- .bss:未初始化的C程序全局变量和静态局部变量。
- .debug:调试符号表,调试器用此段的信息帮助调试。
反汇编ELF
因为ELF无法当作普通文件打开,若希望直接查看一个ELF文件包含的指令和数据,则需要用反汇编方法。
使用objdump -D,对hello进行反汇编,如下所示:
$ objdump -D hello
作者不会汇编且汇编文件过长所以 略 建议读者自己尝试
使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:
$ gcc -o hello -g hello.c
$ objdump -S hello
参考文献
GCC学习总结 --作者:studyingdda
gcc编译器背后的故事 --作者:Loopy睿
GCC 指令详解及动态库、静态库的使用方法 --作者:wyhua2008
总结
在写笔记过程中,参考大家的内容也学习到了很多知识,正所谓温故知新。有不妥之处请各位指点。