链接与符号

链接

链接的本质就是把多个不同编译单元(目标文件)相互整合到一起。整合的方式是通过地址的确定。

从单个编译单元的角度看,自己内部的声明可以按有无定义分为两类。对于有定义的一类,程序执行时自然可以定位地址完成相应操作,也就是地址是已知的。对于无定义的一类,由于是采用的外部的函数或者全局变量,地址是未知的,链接就是给未知的地址进行定位。

在链接中统称函数和变量为符号,链接过程就是以符号为依据进行地址定位。

静态链接:单个编译单元中的引用在未连接前地址设置为0,链接时依据重定位表进行重定位

动态链接:将从动态共享对象中的引用标记为一个动态链接符号(链接时同样需要动态库参与,为了使用动态库的符号表确定哪个引用为动态链接符号)。执行时动态链接器与动态库都被映射到进程地址空间

LLVM IR 链接符号

具体的链接类型在IR阶段表现的更加彻底。这些都是通过源码的各种关键字(如:static,extern),声明定义方式(是否是引用)在IR中进行成显性的表示。

下面内容来自llvm文档 https://llvm.org/docs/LangRef.html#linkage-types

所有全局变量和函数都有一种下列链接类型

private

只能被当前模块的对象直接访问。成为private的一个可能原因是为了避免重命名后的冲突。模块中的私有符号的所有引用都可以被更新,而这不会出现在任何目标文件的符号表中。

internal

private相似,但是会作为一个local符号(比如ELF中STB_LOCAL)加入到目标文件符号表中。这个概念类似于C语言中static关键字。

available_externally

available_externally关键字的全局变量将不会被输出到模块对应的目标文件中。从链接情况看,这相当于external声明。为了给内联和优化的发生提供外部模块全局变量的基础。available_externally的全局变量允许被随意丢弃、内联和优化,只可用于definitions,不可用于declarations。

linkonce

链接发生时将与其他同名全局变量合并。这个用于实现内联、模板或者其他必须在每个使用到它的编译单元都生成的代码,但是在之后可能被更明确的定义所覆盖。没有被引用的linkonce变量可以被丢弃。注意linkonce实际上不允许优化将函数主体内联到调用者中,因为它无法确定该定义是否是最终定义或者会不会被更明确的定义覆盖。使用linkonce_odr可使用内联或其他优化。

weak

weak拥有与linkonce相似的合并语义,不同的是没有引用的变量无法被丢弃。用于C源码中声明为”weak”的全局变量。

common

weak类似,但在C中通常用于全局领域暂定的定义,比如”int x;”。拥有common的符号采用与weak同样的方式合并,并且没有引用也不会被删除。common可能没有一个明确的段,必须有0初始化,并且可能不会被记为’constant’。函数和别名可能不被记为common

appending

仅被用于指向数组类型的全局指针。当两个appending一起链接的时候,这两个全局变量会被附加在一起。这是LLVM类型安全,等价于当.o文件被链接时让系统链接器在段中将同名附加到一起。不幸的是这与任何特性都不相符,所以仅能用于专门的LLVM解释器比如”llvm.global_ctors”

extern_weak

该链接的语义遵循ELF目标文件模型:符号在链接之前是weak,如果没有被链接,这个符号就变成了空,而不是一个未定义的引用。

linkonce_odr, weak_odr

一些语言允许不同的全局变量被合并,比如两个不同语义的函数。其他语言,比如C++,确保只有相同的全局变量被合并(one definition rule – ODR)。可以使用这两种linkage类型来说明只有相同才被合并。其他内容与不加odr的相同。

external

如果没有使用上述符号,说明全局可见,参与链接并可被用于解析外部符号。

关于强弱符号

默认函数和初始化的全局变量为强符号,未初始化以及明确声明属性为weak的全局变量为弱符号。

强符号不允许多次定义;强符号覆盖弱符号;若没有强符号以占用空间最大的弱符号为准。

当符号全为弱符号且可能出现的类型不同时,单个编译单元无法确定符号大小。

common块的目的就是为了存放未确定最终大小的全局变量(可能被覆盖)。等最终链接完成还是会被存放到.BSS段

直接导致需要COMMON机制的原因是编译器和链接器允许不同类型的弱符号存在,但最本质的原因还是链接器不支持符号类型,即无法判断各个符号的类型是否一致。(《程序员的自我修养》P112)

BSS段存放(未初始化或者初始化值为0)的(全局变量和局部静态变量)。BSS段长度由段表保存,符号则由符号表保存,只有记录不占据磁盘空间,加载后占据内存空间

有些编译器会将全局的未初始化变量存放在BSS,有些不存放只预留一个未定义的全局变量符号。

当全局变量初始为0时,尽管为强符号,但是放在BSS段,不占据磁盘空间。

全局变量未初始化,值为0,在common段,加载时确定大小,然后放入BSS段。

note:当变量不在common段时,为强符号,也就是说弱符号等价于在common段。