Linux笔记—gcc/g++与编译链接

1. 简单介绍
GCC(GNU Compiler Collection)是一个广泛使用的编译器集合,支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada和Go等。

GCC中的C编译器通常被称为gcc,而C++编译器则被称为g++。

gcc和g++的区别
gcc:主要用于编译C语言代码。对于文件扩展名为.c的源文件,gcc会将它们作为C代码处理。
g++:主要用于编译C++语言代码。对于文件扩展名为.cpp、.cc、.cxx或.C的源文件,g++会将它们作为C++代码处理。
gcc和g++都是GNU编译器集合的一部分,但g++是专门用于C++编译的,而gcc则是一个更通用的编译器,可以处理C、C++在内的多种语言。在编译C++代码时,使用g++可以避免手动链接C++库的麻烦,并且能够确保C++特性的正确处理。

gcc与g++的使用方式十分相似,接下来我们主要以gcc编译C语言代码为例介绍。

2. 编译源代码
执行下面的指令即可使用gcc编译源代码,生成可执行程序或中间文件:

gcc -o [目标文件] [编译选项] [要编译的文件]
gcc [编译选项] [要编译的文件] -o [目标文件]
[-o]选项用于指定要生成的文件的名称,若未指定则会发生默认行为,这个我们在下文中再进行讲解;[选项]用于选择将要编译的文件编译到哪个阶段。

2.1 编译的四个阶段
编译实际上会经历四个阶段:预处理、编译(整个编译过程中的一步)、汇编、链接。

在进行前三个阶段的过程中,每个阶段结束都会产生一个中间文件,以便下一个阶段接手该阶段的工作:

• 预处理(进行宏替换/去注释/条件编译/头文件展开等)

[.c文件] -> [.i文件]

• 编译(生成汇编代码)

[.i文件] -> [.s文件]

• ​​​​​​​汇编(生成二进制机器码)

[.s文件] -> [.o文件]

• ​​​​​​​链接(将各个[.o文件]及相关库进行链接,生成可执行程序或库文件)

[.o文件] -> [可执行程序或库文件]

可通过下面的选项控制执行到哪个阶段:

• ​​​​​​​[-E]:到预处理阶段完成为止

[未完成预处理的文件] -> [.i文件](不指定目标文件时输出到控制台)

• ​​​​​​​[-S]:到编译阶段完成为止

[未完成编译的文件] -> [.s文件](不指定目标文件时输出到控制台)

• ​​​​​​​[-c]:到汇编阶段完成为止

[未完成汇编的文件] -> [.o文件](不指定目标文件时输出到[同名.o文件])

• ​​​​​​​不加选项:到链接阶段完成为止(结束)

[未完成链接的文件] -> [可执行程序或库文件](不指定目标文件时输出到[a.out文件])

2.1.1 预处理
gcc -E [未完成预处理的文件] -o [.i文件]
在该阶段进行宏替换/去注释/条件编译/头文件展开等:

宏替换:将所有的宏定义用它们的值替换。

文件包含:将#include指令引入的头文件内容直接插入到指令所在的位置。

条件编译:根据条件编译指令(如#if、#ifdef、#ifndef等)的判断,选择性地包含或排除代码块。

删除注释:去除源代码中的所有注释(//和/* */),以便在后续阶段不会被处理。

2.1.2 编译
gcc -S [未完成编译的文件] -o [.s文件]
在该阶段生成汇编代码或目标文件,这个阶段(狭义编译)是整个编译过程(广义编译)最核心的部分,包括对代码进行词法分析,语法分析,语义分析,生成中间代码,中间代码优化等阶段。

有一门课叫做<<编译原理>>(极难),对该阶段进行了详细的介绍,这个阶段不是一篇小小的博文能够讲清楚的,我就不作过多介绍了。

注意,说是生成的汇编代码,但实际上又不完全是,只是很相似。

2.1.3 汇编
gcc -c [未完成汇编的文件] -o [.o文件]
该阶段讲编译阶段生成的汇编代码翻译为可被机器识别并执行的二进制机器码。

也就是说[.o文件]已经可以被机器执行了,但是其中对库的调用,以及对其他文件中定义的函数/类型等的调用并不能完成,因为其还没有与这些文件建立联系。

我们也称这个[.o文件]依赖于其他的[.o文件]或库文件。

2.1.4 链接
gcc [未完成链接的文件] -o [可执行程序或库文件]
这个阶段就是将各个[.o文件]以及库文件进行链接,满足他们之间的依赖关系。

链接的方式有两种:动态链接和静态链接。

2.2 其他选项
下面的选项独立于[-o]选项和编译选项存在,可进一步控制整个编译过程的细节:

• [-static]:此选项对生成的文件采用静态链接。

• [-g]:生成调试信息。GNU 调试器可利用该信息。

• [-shared]:此选项将尽量使用动态库,所以生成文件比较小,但是需要系统有动态库。
• [-O0]、[-O1]、[-O2]、[-O3]:编译器的优化选项的4个级别,[-O0]表示没有优化,[-O1]为缺省值,[-O3]优化级别最高。

• [-w] 不生成任何警告信息。

• [-Wall] 生成所有警告信息。

3. 动态链接和静态链接
3.1 静态链接
静态链接是在编译时将程序所需的所有库代码链接到单个可执行文件的过程。

这意味着整个库代码成为可执行文件的一部分。当程序运行时,它不需要单独访问库代码,因为它已经存在于可执行文件中。这使得可执行文件变大了,但它可以在任何系统上运行,而不需要单独安装库。静态链接通常用于小程序或库代码不太可能经常更改的情况。

优点:

在可执行程序中已经具备了执行程序所需要的所有东西,在执行的时候运行速度快。

缺点:

节浪费空间:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,程序在运行时printf.o会被多次加载到内存中。
更新比较困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
3.2 动态链接
动态链接是在运行时将库代码链接到可执行文件的过程。

这意味着库代码不是可执行文件的一部分,而是作为共享库文件单独存储的(Windows上是.dll,Linux上是.so)。当程序运行时,它在执行过程中动态地访问共享库文件。这导致可执行文件更小,因为库代码没有包含在可执行文件中,但它还要求库文件必须安装在运行程序的系统上。动态链接通常用于大型程序或库代码可能经常更改的情况。

优点:

节省内存和磁盘空间:由于多个程序可以共享同一个动态链接库,因此避免了在每个程序中重复包含相同的代码和数据,从而节省了内存和磁盘空间。
便于更新和维护:当动态链接库更新时,只需要替换掉旧的库文件,而不需要重新编译或链接依赖于该库的程序。这大大提高了软件的更新和维护效率
缺点:

启动时间可能较长:由于动态链接需要在程序运行时加载和解析库文件,因此可能会增加程序的启动时间。特别是当程序依赖于多个大型库时,这个问题会更加明显。
存在版本兼容性问题:如果新版本的动态链接库与旧版本的程序不兼容,那么程序可能会出现运行错误或崩溃的情况。此外,如果程序依赖于多个版本的库文件,还可能会出现DLL地狱(DLL Hell)问题。
3.3 操作系统中的库
• Linux下,动态库XXX.so,静态库XXX.a

• Windows下,动态库XXX.dll,静态库XXX.lib

动态库一般会存放到操作系统指定的目录下,当程序需要链接到对应的库时,系统会到该目录下查找,不同的操作系统有所不同。

使用[ldd]指令可以查看可执行程序依赖的动态库,使用[file]指令可以验证程序使用的链接方式是动态链接还是静态链接

版权声明:
作者:SE_Yang
链接:https://www.cnesa.cn/2396.html
来源:CNESA
文章版权归作者所有,未经允许请勿转载。

THE END
打赏
海报
Linux笔记—gcc/g++与编译链接
1. 简单介绍 GCC(GNU Compiler Collection)是一个广泛使用的编译器集合,支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada和Go等。 GCC中的C编译器……
<<上一篇
下一篇>>