弱链接与使用COMDAT部分之间有什么区别?

时间:2019-08-06 14:54:22

标签: c++ elf linkage

假设我有一个跨多个对象文件定义的对象的多个定义,并且我想将它们链接在一起。似乎有两种方法可以选择将这些目标文件链接在一起时使用的定义:将符号标记为弱,或将其放在其自己的COMDAT部分(在每个目标文件中)。

按照我的理解(可能是错误的)的方式,弱链接允许这种情况,因为一个符号定义会根据链接顺序覆盖另一个符号的定义。通过将符号放在唯一的COMDAT部分中,并选择链接时使用哪个部分,COMDAT允许多种定义。

有人告诉我,这两个概念是相互正交的,但是不确定为什么要同时使用这两个概念而不是仅使用其中一个。举一个更具体的例子,我相信c ++中没有键功能的虚拟表在使用的所有模块中都有定义,并且这些模块可以链接在一起,因为vtable本身声明为弱表,并且在自己的COMDAT中声明。 >

2 个答案:

答案 0 :(得分:0)

  

但不确定为什么要同时使用两者而不是仅使用其中之一。

弱链接通常在功能的不同(即不等效)实现时使用。

例如,libc.a可以提供pthread_mutex_lock定义,该定义什么都不做,而libpthread.a将提供 strong 确实锁定互斥锁的同一函数的定义。

用户代码(main.o)可以盲目调用pthread_mutex_lock,根据是否链接了libpthread.a,将发生正确的事情。

如果链接器可以自由选择pthread_mutex_lock中的任何一个,这显然是行不通的(如果它们都是COMDAT符号,就会发生这种情况)。

答案 1 :(得分:0)

comdat 和弱符号之间的区别,来自 GCC Vague Linkage 文档:

<块引用>

这些结构的重复副本将在链接时被丢弃。这称为 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。这显然导致 objdumpreadelf 无法识别它,它们仅将它们显示为弱符号:

$ 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
...

我们可以通过这个例子验证弱符号和comdat部分的行为:

$ 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%x\n", 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
相关问题