我尝试构建一个链接到各种共享库和静态库的可执行文件。事实证明,两个静态库都定义相同的符号,这会导致多定义链接器错误。我的可执行文件不使用此符号,因此它并不是真正的问题。
我可以通过添加--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.o
和four.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)
。您想知道:不,没有,但这并不重要。不会从
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
允许。鉴于此,您还将看到:
通常不会飞。如果我们可以告诉链接器忽略(说)
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
中看到clash
的libonetwo_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.o
或four.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
,由nm
或readelf
。相反,如果您希望使用objcopy来更改_ZN6onetwo5clashE
,一个目标文件到一个符号,它将作为
onetwo::klash
分解,然后该符号将是
_ZN6onetwo5klashE
。