gpt4 book ai didi

c++ - 不使用重新定义的符号时避免多定义链接器错误

转载 作者:行者123 更新时间:2023-12-03 07:02:37 25 4
gpt4 key购买 nike

我尝试构建一个链接到各种共享库和静态库的可执行文件。事实证明,两个静态库都定义相同的符号,这会导致多定义链接器错误。我的可执行文件不使用此符号,因此它并不是真正的问题。

我可以通过添加--allow-multiple-definitions标志来避免该错误,但这似乎是一个核选项。如果我尝试使用多次定义的符号,我希望链接器抱怨。

有没有办法告诉链接器“仅在使用符号的情况下才对多个定义进行抱怨”?或者,也可以告诉它“从lib ABC忽略符号XYZ”。我正在Linux上使用g++进行开发。

最佳答案

您可能有一个问题的变体或另一个变体,
取决于您尚未考虑的相关事实。或者,可能您将两者混合使用,所以我将逐步介绍每种变体的解决方案。

您应该熟悉静态库的性质以及如何在链接中使用它们,
as summarised here

多余的全局符号变体

这是几个源文件和头文件:

one.cpp

#include <onetwo.h>

int clash = 1;

int get_one()
{
return clash;
}

two.cpp
#include <onetwo.h>

int get_two()
{
return 2;
}

onetwo.h
#pragma once

extern int get_one();
extern int get_two();

这些已内置到静态库 libonetwo.a
$ g++ -Wall -Wextra -pedantic -I. -c one.cpp two.cpp
$ ar rcs libonetwo.a one.o two.o

其预期的API在 onetwo.h中定义

类似地,其他一些源文件和 header
内置到静态API的 libfourfive.a中,该API的预期API
fourfive.h中定义

four.cpp
#include <fourfive.h>

int clash = 4;

int get_four()
{
return clash;
}

五.cpp
#include <fourfive.h>

int get_five()
{
return 5;
}

fourfive.h
#pragma once

extern int get_four();
extern int get_five();

这是依赖于两个库的程序的源代码:

prog.cpp
#include <onetwo.h>
#include <fourfive.h>

int main()
{
return get_one() + get_four();
}

我们尝试这样构建:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(four.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(one.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

遇到符号 clash的名称冲突,因为它是在两个
链接所需的目标文件 one.ofour.o:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 10 entries:
9: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 _Z7get_twov
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z8get_fourv
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 10 entries:
9: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 _Z8get_fivev

我们自己的代码 clash中未引用问题符号 prog.(cpp|o)。您想知道:

Is there a way to tell the linker "complain for multiple definitions only if the symbol is used"?



不,没有,但这并不重要。不会从 one.o中提取 libonetwo.a并链接到程序(如果链接程序不需要它来解析某些符号)。需要它
解决 get_one。同样,它仅链接了 four.o,因为解析 get_four时需要使用它。
因此 clash的冲突定义在链接中。尽管 prog.o不使用 clash
它的确使用了 get_one,后者使用 clash,并且打算在 clash中使用 one.o的定义。
同样, prog.o使用 get_four,后者使用 clash并打算在 four.o中使用不同的定义。

即使每个库和程序都未使用 clash,它的定义事实
必须链接到程序中的多个目标文件中,意味着该程序将包含
它有多种定义,只有 --allow-multiple-definitions允许。

鉴于此,您还将看到:

Or alternatively [is there a way to] tell it, "from lib ABC ignore symbol XYZ".



通常不会飞。如果我们可以告诉链接器忽略(说) clash的定义
four.o中并将符号解析为 one.o(唯一的其他候选者)中的定义,然后 get_four()将在我们的程序中返回1而不是4。那
实际上是 --allow-multiple-definitions的效果,因为它导致了第一个定义
在要使用的链接中。

通过检查 libonetwo.a(或 libfourfive.a)的源代码,我们可以相当自信地发现根
问题的原因。符号 clash已留有外部链接,其中
它只需要内部链接,因为它没有在关联中声明
头文件,在库中无处引用,除了
在定义它的文件中。有问题的源文件应该写为:

one_good.cpp
#include <onetwo.h>

namespace {
int clash = 1;
}

int get_one()
{
return clash;
}

four_good.cpp
#include <fourfive.h>

namespace {
int clash = 4;
}

int get_four()
{
return clash;
}

一切都会很好:
$ g++ -Wall -Wextra -pedantic -I. -c one_good.cpp four_good.cpp
$ readelf -s one_good.o four_good.o | egrep '(File|Symbol|OBJECT|FUNC)'
File: one_good.o
Symbol table '.symtab' contains 11 entries:
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 _ZN12_GLOBAL__N_15clashE
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: four_good.o
Symbol table '.symtab' contains 11 entries:
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 _ZN12_GLOBAL__N_15clashE
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z8get_fourv

$ g++ -o prog prog.o one_good.o four_good.o
$./prog; echo $?
5

由于这样无法重写源代码,因此我们必须将目标文件修改为
同样的效果。该工具是 objcopy
$ objcopy --localize-symbol=clash libonetwo.a libonetwo_good.a

此命令与运行具有相同的效果:
$ objcopy --localize-symbol=clash orig.o fixed.o

在每个目标文件 libonetwo(orig.o)上输出固定的目标文件 fixed.o
并将所有 fixed.o文件归档到新的静态库 libonetwo_good.a中。和 --localize-symbol=clash对每个目标文件的作用是更改
符号 clash(如果已定义),从外部( GLOBAL)到内部( LOCAL):
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 10 entries:

现在,链接器无法在 LOCAL中看到 clashlibonetwo_good.a(one.o)定义。

这足以避免多重定义错误,但是由于 libfourfive.a具有
同样的缺陷,我们也会修复:
$ objcopy --localize-symbol=clash libfourfive.a libfourfive_good.a

然后,我们可以使用固定库成功重新链接 prog
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5

全局符号死锁变体

在这种情况下, libonetwo.a的源和 header 是:

one.cpp
#include <onetwo.h>
#include "priv_onetwo.h"

int inc_one()
{
return inc(clash);
}

two.cpp
#include <onetwo.h>
#include "priv_onetwo.h"

int inc_two()
{
return inc(clash + 1);
}

priv_onetwo.cpp
#include "priv_onetwo.h"

int clash = 1;

int inc(int i)
{
return i + 1;
}

priv_onetwo.h
#pragma once

extern int clash;
extern int inc(int);

onetwo.h
#pragma once

extern int inc_one();
extern int inc_two();

对于 libfourfive.a,它们是:

four.cpp
#include <fourfive.h>
#include "priv_fourfive.h"

int dec_four()
{
return dec(clash);
}

五.cpp
#include <fourfive.h>
#include "priv_fourfive.h"

int dec_five()
{
return dec(clash + 1);
}

priv_fourfive.cpp
#include "priv_fourfive.h"

int clash = 4;

int dec(int i)
{
return i - 1;
}

priv_fourfive.h
#pragma once

extern int clash;
extern int dec(int);

fourfive.h
#pragma once

extern int dec_four();
extern int dec_five();

这些库中的每个库都定义了一些常见的内部构造
在源文件中-( priv_onetwo.cpp | priv_fourfive.cpp)
-这些内部信息是通过私有(private) header 全局声明用于构建库的
-( priv_onetwo.h | priv_fourfive.h)-不随库一起分发。
它们是未记录的符号,但是仍然暴露给链接器。

现在每个库中有两个文件使未定义( UND)引用
到在另一个文件中定义的全局符号 clash:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC|clash)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 _Z7inc_onev
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 _Z7inc_twov
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libonetwo.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash
10: 0000000000000000 19 FUNC GLOBAL DEFAULT 1 _Z3inci
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 _Z8dec_fourv
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 _Z8dec_fivev
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libfourfive.a(priv_fourfive.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash
10: 0000000000000000 19 FUNC GLOBAL DEFAULT 1 _Z3deci

这次我们的程序来源是:

prog.cpp
#include <onetwo.h>
#include <fourfive.h>

int main()
{
return inc_one() + dec_four();
}

和:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(priv_fourfive.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(priv_onetwo.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
clash再次被乘法定义。要在 inc_one中解析 main
链接器需要 one.o,这使其不得不解析 inc,这使得它需要 priv_onetwo.o,其中包含 clash的第一个定义。要在 dec_four中解析 main
链接器需要 four.o,这使其不得不解析 dec,这使得它需要 priv_fourfive.o,其中包含 clash的竞争定义。

在这种情况下, clash具有外部链接不是两个库中的编码错误。
它需要具有外部链接。在任一版本中使用 clash本地化 objcopy的定义 libonetwo.a(priv_onetwo.o)libfourfive.a(priv_fourfive.o)将不起作用。如果我们这样做
链接将成功,但会输出错误的程序,因为链接器将解析 clash到另一个目标文件中存在的一个 GLOBAL定义:然后是 dec_four()将在程序中返回0而不是3, dec_five()将返回1而不是4;否则 inc_one()将返回5,而 inc_two()将返回6。
如果我们将这两个定义都本地化,那么在列表中将找不到 clash的定义。
链接 prog以满足 one.ofour.o中的引用,并且对 clash的 undefined reference 将失败

这次, objcopy再次得到救援,但是具有不同的选项1:
$ objcopy --redefine-sym clash=clash_onetwo libonetwo.a libonetwo_good.a

该命令的作用是创建一个新的静态库 libonetwo_good.a
包含与 libonetwo.a中成对相同的新对象文件,
除了符号 clash已经到处都被 clash_onetwo代替:
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|clash)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 13 entries:
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash_onetwo
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 13 entries:
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash_onetwo
File: libonetwo_good.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash_onetwo

我们将使用 libfourfive.a进行相应的操作:
$ objcopy --redefine-sym clash=clash_fourfive libfourfive.a libfourfive_good.a

现在,我们可以再次进行以下操作:
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5

在这两种解决方案中,如果存在,请使用 的修复程序The Superflous Globals Symbols Variant
尽管 已修复全局符号死锁变体,但您已经拥有了多余的全局变量
也可以。篡改目标文件永远是不可取的
在编译和链接之间;它只能是不可避免的,也可以是邪恶的一种。但
如果要篡改它们,请定位一个本不应该是全局的全局符号
与将符号名称更改为
没有源代码。

[1]如果您想将 objcopy与任何选项参数一起使用,请不要忘记
是C++目标文件中的符号,您必须使用
C++标识符然后映射到该符号。在此演示代码中,碰巧的是
C++标识符 clash也是 clash。但是如果完全合格的识别者
onetwo::clash所示,其错误名称为 _ZN6onetwo5clashE,由 nmreadelf。相反,如果您希望使用objcopy来更改 _ZN6onetwo5clashE
一个目标文件到一个符号,它将作为 onetwo::klash分解,然后该符号将
_ZN6onetwo5klashE

关于c++ - 不使用重新定义的符号时避免多定义链接器错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61663118/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com