弱链接与使用 COMDAT 部分有什么区别?
What's the difference between weak linkage vs using a COMDAT section?
假设我在不同的对象文件中定义了对象的多个定义,我想将它们链接在一起。将这些对象文件链接在一起时,似乎有两种方法可以选择使用哪种定义:将符号标记为弱符号,或将其放在其自己的 COMDAT 部分中(在每个目标文件中(。
我的理解方式(这可能是错误的(,弱链接允许通过一个符号的定义覆盖另一个符号的定义,这取决于链接顺序。COMDAT 允许多个定义,方法是将符号放置在唯一的 COMDAT 部分中,并选择链接时使用哪个部分。
有人告诉我,这些是彼此正交的概念,但不确定为什么人们会同时使用两者而不是其中之一。举一个更具体的例子,我相信 c++ 中没有键函数的虚拟表在它们使用的所有模块中都定义了,并且这些模块可以链接在一起,因为 vtable 本身被声明为弱表,并且在它自己的 COMDA 中。
但不确定为什么会同时使用两者而不是其中之一。
当函数存在不同的(即不等效的(可能实现时,最常使用弱链接。
例如,libc.a
可以提供pthread_mutex_lock
的弱定义,该定义不执行任何操作,而libpthread.a
将提供实际锁定互斥锁的同一函数的强定义。
用户代码(main.o
(可以盲目调用pthread_mutex_lock
,正确的事情会发生,这取决于libpthread.a
是否被链接。
如果链接器可以自由选择任一pthread_mutex_lock
,这显然不起作用(如果它们都是 COMDAT 符号,就会发生这种情况(。
comdat 和弱符号之间的区别,来自 GCC 模糊链接文档:
这些构造的重复副本将在链接时被丢弃。这称为 COMDAT 支持。
在不支持 COMDAT 但支持弱符号的目标上,GCC 使用它们。这样,一个副本将覆盖所有其他副本,但未使用的副本将覆盖 副本仍占用可执行文件中的空间。
所以它们的行为是一样的,但 comdat 方法不会使用不必要的空间。上面的文档还描述了编译器何时发出这些(对于内联函数、VTable、模板实例化等(。
编译器(g++ 和 clang(将同时发出一个 comdat 部分和一个弱符号(comdat 部分就足够了,但我想它会同时发出两者,以防链接器不支持 comdat 部分(:
$ cat tt1.cpp
struct s1 {
virtual void f1() {}
};
s1 s1;
$ g++ -S tt1.cpp
$ cat tt1.s | c++filt
.section .text. ... ,s1::f1(),comdat
.weak s1::f1()
...
.section .data. ... ,vtable for s1,comdat
.weak vtable for s1
...
请注意,在生成的程序集中,comdat 被定义为 .section ... comdat
而不是 .comdat
。这显然导致objdump
和readelf
不承认它,他们只将它们显示为弱符号:
$ g++ -c tt1.cpp && readelf -s tt1.o | c++filt
...
17: 0000000000000000 11 FUNC WEAK DEFAULT 8 s1::f1()
19: 0000000000000000 24 OBJECT WEAK DEFAULT 11 vtable for s1
...
我们可以通过这个例子来验证弱符号和通信部分的行为:
$ cat aa.s
.data
.globl var1
.type var1, @object
var1:
.byte 0xb
$ cat aa2.s
.data
.globl var1
.type var1, @object
var1:
.byte 0xc
$ cat aa3.c
#include <stdio.h>
extern char var1;
int main() {
printf("var1 = %0x%xn", var1);
}
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o
aa.o:(.data+0x0): multiple definition of `var1'
aa2.o:(.data+0x0): first defined here
# symbols are neither weak nor in comdat,
# as expected we got multiple definition error
$ cat aa.s
.data
.globl var1
.weak var1
.type var1, @object
var1:
.byte 0xb
$ cat aa2.s
.data
.globl var1
.weak var1
.type var1, @object
var1:
.byte 0xc
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o && ./a.out
var1 = 0xc
# change order of aa.o and aa2.o
$ gcc aa3.c aa.o aa2.o && ./a.out
var1 = 0xb
# only weak symbols, it worked
$ objdump -D ./a.out
...
0000000000201010 <var1>:
201010: 0b .byte 0xb
201011: 0c .byte 0xc
# both values are emitted, only the fist is used
$ cat aa.s
.data
.section .data.var1,"aG",@progbits,var1,comdat
.globl var1
.type var1, @object
var1:
.byte 0xb
$ cat aa2.s
.data
.section .data.var1,"aG",@progbits,var1,comdat
.globl var1
.type var1, @object
var1:
.byte 0xc
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o && ./a.out
var1 = 0xc
# change order
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa.o aa2.o && ./a.out
var1 = 0xb
# only comdat, it worked
$ objdump -D ./a.out
0000000000201010 <var1>:
201010: 0b .byte 0xb
# only a single value is emitted
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- Qt:remove() 和 rmdir() 有什么区别
- 这 4 个 lambda 表达式之间有什么区别?
- 将向量作为类>(值)<向量启动和向量<类>[值]有什么区别
- typedef 枚举和枚举类有什么区别?
- &C::c 和 &(C::c) 有什么区别?
- ascii 和 unicode 在处理级别有什么区别吗?
- C 中的常量限定符和 C++ 中的常量限定符有什么区别?
- "ABC" 和 "ABC" ) 在C++中有什么区别?
- 空指针常量 (nullptr)、空指针值和空成员指针值之间有什么区别?
- 引用捕获和在 lambda 中通过引用发送参数有什么区别 (C++)
- 两种访问I2C总线的方法有什么区别?
- 两种模板示例有什么区别?
- 这两种C++语法之间有什么区别?
- lua 5.0.2 模块和 5.3.5 有什么区别?
- C++中"typedef"、"using"、"namespace"和"using namespace"有什么区别?
- std::enable_if 和 std::enable_if_t 有什么区别?