从零开始学gcc

github学习示例

learn-gcc-makefile

GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言编译器。GNU编译器套件包括C、C++、 Objective-C、 Fortran、Java、Ada和Go语言前端,也包括了这些语言的库(如libstdc++,libgcj等。)

GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的自由软件。此处,“自由”的含义是它尊重用户的自由

历史和特点

计算机软件作为人类的知识财富,为人类社会的发展起到了巨大的作用,但长期以来软件源码作为个人或公司的私有财产受到严格的保密,很难做到像文学艺术作品一样地进行公开的交流,很大程度上造成软件的低水平,重复劳动严重,在一定意义上制约了软件的发展。

GPL 的诞生

1985 年由 MIT 教授理查德·斯托曼(Richard Stallman)提出应将软件源码看成人类共同拥有的知识财富,应该公开地自由交换、修改,提出了 GNU 计划(因英文名相同,GNU 的 logo 就是一只牛羚),并建立了自由软件基金会;同时,发布了一份举足轻重的法律文件,GNU 通用公共授权书(GNU GPL, GNU General Public License)。

该授权书主要有以下几点:

  • 自由软件(free software)指的是源码自由,不是价格;
  • 自由软件必须附带程序源代码,但可收取费用;
  • 任何人都可以自由分发自由软件并收取费用,但必须列明原创者姓名;
  • 任何人都可以修改源代码,但必须列明修改人名字,以保护原创者名誉;
  • 任何人都可以采用源代码中的某一段,但其开发之软件必须也为自由软件(例如,如果 Netscap 是自由软件,而 IE 采用了其中的部份源代码,则 IE 也必须成为自由软件);
  • 任何自由软件的衍生品也必须是自由软件;
  • 自由软件没有担保,以保护分发者。

1991 年,Richard Stallman 对授权做了微小的修改,即所谓的通用公共授权第 2 版。同时,他也推出了更宽松的通用公共授权,用于自由程序库。这一系列的授权有效地保护了自由软件不受商业软件的非法侵犯,例如,1998 年 Netscap 决定采用与 GPL 差不多的 NPL(Netscap Public Liscense),这样一来,Microsoft 就无法将 Netscap 中的源代码运用在 IE 上,除非它们也要成为自由软件。

至此,在 GPL 下人们就可以自由交流、修改软件源码了,这一协议极大地推动了整个计算机软件行业的发展,并带来了以下明显的益处:

  • 对于广大计算机软件的学习者来说,可以直接从源码中吸取营养,缩短学习的时间,提高学习的效率,少走弯路,再也不必花大量时间去看那些不知正确与否的“未解之谜”了,学习在某种程度上变成了一件轻松愉快的事情了。
  • 可以集中大家的智慧发展软件,避免重复劳动。一个软件只有公开源码,通过很多人的研究才有可能发现其中深藏的错误,大家才能公开探讨相关的问题,并进行改进,在大家的共同“挑剔与监督”下才有可能编写出尽善尽美的软件来。

GPL 协议的核心就是要对源码进行公开,并且允许任何人修改源码,但是只要使用了 GPL 协议的软件源码,其衍生软件也必须公开源码,准许其他人阅读和修改源码,即 GPL 协议具有继承性。

另一个问题就是 GPL 软件并非就是免费软件,这里所说的自由软件是指对软件源码的自由获得与自由使用、修改,软件开发者不但可以通过服务来收费,而且还可以通过出售 GPL 软件来获利。

适应 GPL 协议的软件一般都是自由软件,自由软件是指一件可以让用户自由复制、使用、研究、修改、分发等,而不附带任何条件的软件。

copyleft 授权

tallman 为了停止中间人对自由软件权利的侵害,提出了 copyleft 授权,因为自由软件在发布过程中可能会有一些不合作的人通过对程序的修改而将软件变成私有软件,将程序变成 copyleft 授权。

我们首先声明它是有版权的,而后加人了分发条款,这些条款是法律指导,使得任何人都拥有对这一程序代码或者任何这一程序的衍生品的使用、修改和重新发布的权力,但前提是这些发布条款不能被改变。这样在法律上,代码和自由就不可分割了。

自由软件的支持者相信,总有一天,随着自由软件的日渐成熟,自由软件终将主宰整个软件行业,人们不再受少数商业软件公司的控制,真正实现“市集式开发模式”

GNU计划

GNU 项目计划的主要目的是创建一个名叫 GNU’s Not Unix(GNU) 的完全免费的操作系统。该操作系统将包括绝大多数自由软件基金会所开发的其他软件,以对抗所有商业软件,而这个操作系统的核心(kernel)就叫 HURD。

但是 GNU 在开发完全免费的操作系统上并未取得成功,直到 20 世纪 90 年代由林纳斯·本纳第克特·托瓦兹(Linus Benedict Torvalds)开发了Linux操作系统,GNU 才算在免费操作系统上完成了任务。

虽然 GNU 计划在开发免费操作系统上不成功,但是却成功开发几个广为流传的 GNU 软件,其中最著名的是 GNU C Complier(gcc)。

这个软件成为历史上最优秀的C语言编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%,使得那些靠贩卖编译器的公司大吃苦头,因为它们无法研制出与 gcc 同样优秀,却又完全免费、并开放源代码的编译器来。

而由于它又是 copylefted,所以一旦有用户发现错误,就会通知 Richard Stallman,所以几乎每个月都可以推出新版本。然而,它还有一个十分特殊而且不同寻常的意义:几乎所有的自由软件都是通过它编译的。可以说,它是自由软件发展的基石与标杆。

现在,gcc 已经可以支持 7 种编程语言和 30 种编程结构,是学术界最受欢迎的编译工具。

其他 GNU 软件还包括 GNU emacs、GNU Debugger(GDB)、GNU Bash 以及大部分 Linux 系统的程序库和工具等。

目前,gcc 已发展到了 8.x 的版本,几乎所有开源软件和自由软件中都会用到,因此它的编译性能会直接影响到 Linux、Firefox、OpenOffice.org、Apache 以及一些数不清的小项目的开发。gcc 无疑处在开源软件的核心地位。

作为自由软件的旗舰项目,Richard Stallman 在十多年前刚开始写作 gcc 的时候,还只是把它当作一个C程序语言的编译器;gcc 的意思也只是 GNU C Compiler 而已。经过这么多年的发展,gcc 已经不仅仅能支持C语言,它现在还支持 Ada、C++、Java、Objective-C、Pascal、COBOL 以及函数式编程和逻辑编程的 Mercury 语言等。因此,现在的 gcc 已经变成了 GNU Compiler Collection,也即是 GNU 编译器家族的意思了。这个名称同时也说明了 gcc 对于各种硬件平台无所不在的支持,甚至包括一些生僻的硬件平台。

gcc 不仅功能非常强大,结构也异常灵活。最值得称道的一点就是,它可以通过不同的前端模块来支持各种语言,如 Java、Fortran、Pascal、Modula-3 和 Ada 语言等。

GUN 虽然没有开发出操作系统,但是却开发出了很多系统级的自由软件,GCC就是其中之一。

GCC

GNU 在开发完全免费的操作系统上并未取得成功,直到 20 世纪 90 年代由林纳斯·本纳第克特·托瓦兹(Linus Benedict Torvalds)开发了Linux 操作系统,GNU 才算在免费操作系统上完成了任务。

虽然 GNU 计划在开发免费操作系统上不成功,但是却成功开发几个广为流传的 GNU 软件,其中最著名的是 GNU C Complier(gcc)。

GCC 原来代表“GNU C Compiler”的意思。自从面世后,GCC 逐渐扩充、发展,现在不仅仅支持C语言,还支持其他很多语言,包括C++、Ada、Objective-C、Fortran 和 Java等。因此,GCC 的意思被重新定义为“GNU Compiler Collection”,也即“GUN 编译器套件”。

GUN 编译器套件包含多种前端处理器,以翻译各种不同语言。当然,在本教程中我们重点讨论的是基于C语言的前端处理器 GCC。

GCC 也是一种多目标(multitarget)编译器;换句话说,它通过使用可互换的后端处理器,为多种不同的计算机架构生成相应的可执行程序。

正如模块化概念所提倡的,GCC 可被用作交互式编译器;也就是说,可以使用 GCC 对所有设备与操作系统创建可执行程序,不需要局限于仅仅是运行 GCC 的平台。然而,这么做需要特殊的配置和安装,大多数 GCC 的安装,仅能针对它们的宿主系统编译程序。

GCC 不仅支持C的许多“方言”,也可以区别不同的C语言标准;也就是说,可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个C标准。

例如,当使用命令行参数-std=c99启动 GCC 时,编译器支持 C99 标准。GCC 对 C11 标准的支持是不完整的,尤其是涉及定义在头文件 threads.h 中的多线程函数。这是因为,GCC 的C链接库长期以来支持 POSIX 标准下与 C11 标准非常相似的多线程功能。

基本特点

  • GCC是一个可移植的编译器,可以运行在当前的很多平台上,也可以为大部分的平台编译程序。
  • GCC不是一个本地编译器。可以在一个平台上编译另一个平台运行的软件。例如可以在windows平台上编译Linux的可执行文件。
  • GCC支持多种语言,甚至可以交叉编译不同语言。
  • GCC是模块化的,当出现新的语言,只需要为GCC开发一个支持该语言的前端,GCC即可支持该语言。
  • GCC是一个免费自由软件,你可以免费使用GCC,也可以自己修改GCC的内容。

基本使用技巧

通用说明

//c:create r:replace t:table of OBJ file
ar cr libNAME.a file1.o file2.o ... filen.o
ar r libNAME.a

//库文件有次序问题,需要放在源文件后面
gcc -Wall main.c libhellow.a -o calc
gcc -Wall main.c -I. -L. -lhellow -o calc

编译路径优先级

  1. 命令行里的-I和-L
  2. 环境变量C_INCLUDE_PATH CPLUS_INCLUDE_PATH LIBRARY_PATH
  3. 系统目录

执行路径优先级

  1. 环境变量 LD_LIBRARY_PATH,mac下是DYLD_LIBRARY_PATH
  2. 系统目录,在/etc/ld.so.conf /etc/ld.so.conf.d/*.conf 中指定

gcc命令常用选项

选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串"1"定义 MACRO 宏。
-DMACRO=DEFN 以字符串"DEFN"定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成所有警告信息。

组成部分

下表列出了 GCC 的各个部分,但它们也并不总是出现的。有些部分是和语言相关的,所以如果没有安装某种特定语言,系统中就不会出现相关的文件。

部分 描述
c++ gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 g++ 一样
ccl 实际的C编译程序
cclplus 实际的 C++ 编泽程序
collect2 在不使用 GNU 连接程序的系统上,有必要运行 collect2 来产生特定的全局初始化代码(例如 C++ 的构造函数和析构函数)
configure GCC 源代码树根目录中的一个脚木。用于设置配置值和创建GCC 编译程序必需的 make 程序的描述文件
crt0.o 这个初始化和结束代码是为每个系统定制的,而且也被编译进该文件,该文件然后会被连接到每个可执行文件中来执行必要的启动和终止程序
cygwin1.dll Windows 的共享库提供的 API,模拟 UNIX 系统调用
f77 该驱动程序可用于编译 Fortran
f771 实际的 Fortran 编译程序
g++ gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 c++ 一样
gcc 该驱动程序等同于执行编译程序和连接程序以产生需要的输出
gcj 该驱动程序用于编译 Java
gnat1 实际的 Ada 编译程序
gnatbind 一种工具,用于执行 Ada 语言绑定
gnatlink 一种工具,用于执行 Ada 语言连接
jc1 实际的 Java 编译程序
libgcc 该库包含的例程被作为编泽程序的一部分,是因为它们可被连接到实际的可执行程序中。 它们是特殊的例程,连接到可执行程序,来执行基木的任务,例如浮点运算。这些库中的例程通常都是平台相关的
libgcj 运行时库包含所有的核心 Java 类
libobjc 对所有 Objective-C 程序都必须的运行时库
libstdc++ 运行时库,包括定义为标准语言一部分的所有的 C++ 类和函数

下表列出的软件和 GCC 协同工作,目的是实现编译过程。有些是很基本的(例如 as 和 Id),而其他一些则是非常有用但不是严格需要的。尽管这些工具中的很多都是各种 UNIX 系统的本地工具,但还是能够通过 GNU 包 binutils 得到大多数工具。

工具 描述
addr2line 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻泽成源代码文 件名和行号。该程序是 binutils 包的一部分
ar 这是一个程序,可通过从文档中增加、删除和析取文件来维护库文件。通常使用该工具是为了创建和管理连接程序使用的目标库文档。该程序是 binutils 包的一部分
as GNU 汇编器。实际上它是一族汇编器,因为它可以被编泽或能够在各种不同平台上工作。 该程序是 binutils 包的一部分
autoconf 产生的 shell 脚木自动配置源代码包去编泽某个特定版木的 UNIX
c++filt 程序接受被 C++ 编泽程序转换过的名字(不是被重载的),而且将该名字翻泽成初始形式。 该程序是 binutils 包的一部分
f2c 是 Fortran 到C的翻译程序。不是 GCC 的一部分
gcov gprof 使用的配置工具,用来确定程序运行的时候哪一部分耗时最大
gdb GNU 调试器,可用于检查程序运行时的值和行为
GNATS GNU 的调试跟踪系统(GNU Bug Tracking System)。一个跟踪 GCC 和其他 GNU 软件问题的在线系统
gprof 该程序会监督编泽程序的执行过程,并报告程序中各个函数的运行时间,可以根据所提供 的配置文件来优化程序。该程序是 binutils 包的一部分
ld GNU 连接程序。该程序将目标文件的集合组合成可执行程序。该程序是 binutils 包的一部
libtool 一个基本库,支持 make 程序的描述文件使用的简化共享库用法的脚木
make 一个工具程序,它会读 makefile 脚木来确定程序中的哪个部分需要编泽和连接,然后发布 必要的命令。它读出的脚木(叫做 makefile 或 Makefile)定义了文件关系和依赖关系
nlmconv 将可重定位的目标文件转换成 NetWare 可加载模块(NetWare Loadable Module, NLM)。该 程序是 binutils 的一部分
nm 列出目标文件中定义的符号。该程序是 binutils 包的一部分
objcopy 将目标文件从一种二进制格式复制和翻译到另外一种。该程序是 binutils 包的一部分
objdump 显示一个或多个目标文件中保存的多种不同信息。该程序是 binutils 包的一部分
ranlib 创建和添加到 ar 文档的索引。该索引被 Id 使用来定位库中的模块。该程序是 binutils 包的一部分
ratfor Ratfor 预处理程序可由 GCC 激活,但不是标准 GCC 发布版的一部分
readelf 从 ELF 格式的目标文件显示信息。该程序是 binutils 包的一部分
size 列出目标文件中每个部分的名字和尺寸。该程序是 binutils 包的一部分
strings 浏览所有类型的文件,析取出用于显示的字符串。该程序是 binutils 包的一部分
strip 从目标文件或文档库中去掉符号表,以及其他调试所需的信息。该程序是 binutils 包的一部
vcg Ratfor 浏览器从文木文件中读取信息,并以图表形式显示它们。而 vcg 工具并不是 GCC 发布中的一部分,但 -dv 选项可被用来产生 vcg 可以理解的优化数据的格式
windres Window 资源文件编泽程序。该程序是 binutils 包的一部分

gcc和++

只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别,比如:

  • xxx.c:默认以编译 C 语言程序的方式编译此文件;
  • xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
  • xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
  • xxx.go:默认以编译 Go 语言程序的方式编译此文件;

当然,gcc 指令也为用户提供了“手动指定代表编译方式”的接口,即使用 -x 选项。例如,gcc -xc xxx 表示以编译 C 语言代码的方式编译 xxx 文件;而 gcc -xc++ xxx 则表示以编译 C++ 代码的方式编译 xxx 文件。有关 -x 选项的用法,后续会给出具体样例。

但如果使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译。

C++ 标准对代码书写规范的要求更加严格。除此之外对于编译执行 C++ 程序,使用 gcc 和 g++ 也是有区别的。要知道,很多 C++ 程序都会调用某些标准库中现有的函数或者类对象,而单纯的 gcc 命令是无法自动链接这些标准库文件的。如果想使用 gcc 指令来编译执行 C++ 程序,需要在使用 gcc 指令时,手动为其添加 -lstdc++ -shared-libgcc 选项,表示 gcc 在编译 C++ 程序时可以链接必要的 C++ 标准库。

读者可以这样认为,g++ 指令就等同于gcc -xc++ -lstdc++ -shared-libgcc指令。显然后者书写是非常麻烦的,大多数人会更喜欢前者。

链接库的使用

一般来说我们写代码时调用的那些系统中自带的库函数都不是.c或是.cpp这些明文的文件,而是一个个封装起来的库文件.通常情况下GCC在编译过程中默认使用动态链接库,我们可以使用 -static 选项指定使用静态链接库

静态链接库通常可以使用 ar rcs 静态链接库名称 目标文件1 目标文件2 ... 方式将.o文件打包成名称为 libxxx.a 的静态链接库

静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。 使用静态链接库生成的文件通常体积较大,但运行时不再依赖库文件,可以拷贝到别的设备上运行。 在Linux中静态链接库文件的后缀名通常用.a表示;在Windows系统中,静态链接库文件的后缀名为.lib。

参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。

参数c:创建一个库。不管库是否存在,都将创建。

参数s:创建目标文件索引,这在创建较大的库时能加快时间。(补充:如果不需要创建索引,可改成大写S参数;如果。a文件缺少索引,可以使用ranlib命令添加)

动态链接库通常可以使用 gcc -fpic -shared 源文件名... -o 动态链接库名 方式将源文件打包成名称为 libxxx.so 的动态链接库(也可以拆分为 gcc -c -fpic ...gcc -shared -o ... 两步;如果编译时出错也可以尝试将 -fpic 换成 -fPIC)

动态链接库,又称为共享链接库。和静态链接库不同,采用动态链接库实现链接操作时,程序文件中哪里需要库文件的功能模块,GCC 编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。 使用动态链接库生成的文件通常体积较小,但运行时依赖库文件。 在Linux中,动态链接库的后缀名通常用.so 表示;在Windows系统中,动态链接库的后缀名为.dll。

-fpic 产生位置无关代码 -shared 共享

一般的像是头文件、库文件的路径或是指定的文件这些都可以手动设置,相关选项如下:

选项 功能
-I (大写i) 指定头文件路径
-i 指定头文件名字
-L 指定库文件路径
-l (小写L) 指定库文件名字

基本代码优化

源码优化

源码层面,和机器无关。这里有两种优化方式

Common Suexpression Elimination(CSE):公用表达式消除,复用已有结果。CSE会自动执行当优化打开时

Function Inlining(FL):函数内敛,取消函数调用

Loop Unrolling:循环优化,以空间换时间(Tradeoff)

机器优化

Scheduling:1.决定机器指令最佳排列顺序 2.尽可能多的机器指令并行

gcc优化技巧

-O0~3:等级越高,优化强度越大,编译越慢,调试越难。0表示不优化,3表示最大优化

通常使用-O0用来调式,-O2用来发布

-funroll-loops:开启空间换时间

gcc可以同时开启-g和-O,这表示优化的同时可以调试

优化工具

gprof. gcc -Wall -pg 即可开启gp检测.运行代码后,会生成结果,使用 pgrof 查看每个函数总运行时间

gprof实际上只是一个用于读取profile结果文件的工具。gprof采用混合方法来收集程序的统计信息,他使用检测方法,在编译过程中在函数入口处插入计数器用于收集每个函数的被调用情况和被调用次数;也使用采样方法,在运行时按一定间隔去检查程序计数器并在分析时找出程序计数器对应的函数来统计函数占用的时间。

gprof -b -A -p -q test gmon.out > pg

gcov. gcc -Wall -fprofile-arcs -ftest-coverage 即可开启gc检测. 运行代码后,会生成结果,查看每一行代码执行次数

编译器工作原理

部分约定规则

.c为后缀的文件,C语言源代码文件;

.a为后缀的文件,是由目标文件构成的档案库文件;

.C,.cc或.cxx 为后缀的文件,是C++源代码文件;

.h为后缀的文件,是程序所包含的头文件;

.i 为后缀的文件,是已经预处理过的C源代码文件;

.ii为后缀的文件,是已经预处理过的C++源代码文件;

.m为后缀的文件,是Objective-C源代码文件;

.o为后缀的文件,是编译后的目标文件;

.s为后缀的文件,是汇编语言源代码文件;

.S为后缀的文件,是经过预编译的汇编语言源代码文件。

基本处理流程

20221006183413

可以使用一些选项来控制这些过程:

选项 功能
-E 预处理指定的源文件,不进行编译
-S 编译指定的源文件,但是不进行汇编
-c 编译、汇编指定的源文件,但是不进行链接
-o 指定生成文件的文件名
#cpp hello.c > hello.i
#生成预处理后的文件.i
cpp hello.c >hello.i
gcc -E hello.c > hello.i
#生成汇编后的文件.s
gcc -Wall -S -masm=intel
#生成编译后的文件.o
as hello.s
gcc -c hello.s
#生成可执行文件
gcc hello.o

使用gcc一步到位方式编译时每次进行所有文件都需要经历预处理、编译、汇编、链接几个环节,这在文件多的情况下会非常耗时。如果程序经常修改,每次所有文件都需要重复上面过程的话会非常浪费时间。通常情况下我们将整个过程拆分成两步:

  • 使用 -c 选项生成 *.o 的文件;
  • 链接生成目标文件;

这样的话每次只需要对有修改的文件使用 -c 选项生成 *.o 的文件,然后再链接就行,其它没修改的文件就不需要再预处理、编译、汇编了,对大项目来说可以节省很多时间。

符号链接流程

符号定义的本质是:指被分配了存储空间。如果是函数名则指代码所在区;如果是变量名则指其所在的静态数据区。所有定义的符号的值就是其目标所在的首地址。

因此,符号的解析就是将符号引用和符号定义建立关联后,将引用符号的地址重定位为相关联的符号定义的地址。

每个可重定位目标模块m都有一个符号表,它包含了在m中定义和引用的符号。有三种链接器符号

  • Global symbols(模块内部定义的全局符号)
  • External symbols(外部定义的全局符号)
  • Local symbols(本模块的局部符号)

20221007125922

强符号:函数名和已初始化的全局变量名是强符号。弱符号:未初始化的全局变量名是弱符号。

符号解析时,只能有一个确定的定义(即每个符号仅占一处存储空间)。所以,如果碰到符号存在多重定义时,就得有相应的处理规则:

  1. 强符号不能多次定义.强符号只能被定义一次,否则链接错误。
  2. 若一个符号被定义为一次强符号和多次弱符号,则按强符号定义为准。
  3. 若有多个弱符号定义,则任选其中一个。

使用命令 gcc -fno-common 链接时,会告诉链接器在遇到多个弱定义的全局符号时输出一条警告信息。

目标文件的.symtab节记录着符号表信息,符号表示一个结构体数组,每个表项(16字节)的结构如下:

20221007131226

我们可以通过file命令来查看文件的类型

ldd 命令可以显示可执行文件所依赖的动态库