2017-05-05 50 views
1

此问题以前曾询问过gcc,但达尔文的ld(clang?)似乎可以解决这个问题。xcode ld检测静态库中的重复符号

假设我在两个文件main1.cc和main2.cc中定义了一个main()函数。如果我尝试这两种编译这些了,我会得到(预期的)符号重复错误:

$ g++ -o main1.o -c main1.cc 
$ g++ -o main2.o -c main2.cc 
$ g++ -o main main1.o main2.o 
duplicate symbol _main in: 
    main1.o 
    main2.o 
ld: 1 duplicate symbol for architecture x86_64 
clang: error: linker command failed with exit code 1 (use -v to see invocation) 

但如果我坚持,而不是其中的一个成一个静态库,当我去我赢了”链接应用程序吨得到一个错误:

$ ar rcs libmain1.a main1.o 
$ g++ -o main libmain1.a main2.o 
(no error) 

与海湾合作委员会,你可以用--whole-archive包裹的lib,然后gcc的LD将产生错误。该选项不适用于带有w/xcode的ld。

是否有可能让ld打印错误?

+0

'main()'是一个全局函数,它是程序的入口点。每个过程都是程序的一个实例。 如果你只是链接两个目标文件,然后生成最终的可执行程序,它只能有一个'main()'函数。 如果链接一个静态库,入口点是'main2.o'中的'main()'函数。链接器将复制粘贴'main2.o'所需的所有外部代码,但实际上存在于'libmain1.a'中的 。 'mainmain.a'中的'main()'函数在finally程序main中不存在。没关系。 – merito

回答

2

我确定你知道你不应该在静态库中放置一个包含main函数的对象文件 。如果我们的读者 没有:库是用于包含可能被许多程序重用的函数。程序只能包含一个main函数,可能性小于程序的函数将可重用为另一函数的功能。所以main函数不会在库中。 (这条规则有一些奇怪的例外)。

然后解决你所担心的问题。为了简单起见, 我将在本文的其余部分中排除共享/动态库的考虑。

你的链接器检测的联动重复符号错误(又名多个定义错误) 当竞争的定义是不同的输入目标文件 但是当一个定义为输入对象文件和其他 没有检测到在输入静态库中。在这种情况下,如果在静态库中传递--whole-archive选项--whole-archive之前,GNU链接程序可以检测到 多重定义的符号。但是,您的链接器Darwin Mach-O linker, 没有该选项。

请注意,虽然您的链接器不支持--whole-archive,但它有一个 等效选项-all_load。但不要逃避,因为担心无论如何都毫无根据。对于这两种接头:

  • 真的有多个定义错误的联动在[foo.o ... bar.o]情况。

  • 真的是在联动多个定义错误[foo.o ... libbar.a]情况。

而且除了用于GNU链接:

  • 真的有在 [foo.o ... --whole-archive libbar.a]情况下,联动多个定义错误。

在任何情况下,任何链接器都不允许多个定义的符号到 进入您的程序未被发现并任意使用其中之一。

链接foo.o和链接libfoo.o有什么区别?

链接器将只添加目标文件到您的程序。 更确切地说,当它遇到输入文件foo.o时,它会将foo.o中的所有符号引用和符号定义添加到您的程序 中。 (至少对于初学者 至少:如果你已经要求,它可能会最终丢弃未使用的定义, ,并且如果它可以这样做,而没有附带丢弃任何旧定义)。

静态库只是一大堆目标文件。当链接器遇到输入文件 libfoo.a时,默认情况下它不会将任何包中的对象文件添加到 您的程序中。

它只会检查袋子的内容,如果它必须在联系的那一点。

它会必须检查包的内容,如果它已经添加 一些符号引用到您的程序没有定义。那些 未解决的符号可能在包中的一些目标文件中定义。

如果需要查看包中的内容,则它将检查目标文件为 ,看看它们中是否包含 程序中未解析符号的定义。如果有任何这样的对象文件,那么它会将它们添加到程序中,并重新考虑是否需要继续查看包。当它找不到程序需要的更多目标文件或找到程序引用的所有符号的定义时,它会停止在包中查找,以先到者为准。

如果需要包中的任何目标文件,这会在程序中再添加至少一个符号 定义,还可能会添加更多未解决的符号。然后链接器继续。 一旦它遇到libfoo.a,并认为,如果有的话,在联动 序列对象文件,它需要你的程序, 不会再考虑它,除非它符合它再次那个包,后来。

所以...

案例1。输入文件包含[foo.o ... bar.o]。 foo.obar.o 定义符号A。两个目标文件都必须链接,所以A的两个定义都必须添加到程序中,这是一个多重定义错误。两个链接器都检测到它。

案例2输入文件包含[foo.o ... libbar.a]。

  • libbar.a包含目标文件a.ob.o
  • foo.o定义了符号A和引用,但没有定义符号B
  • a.o还定义了A,但未定义B,并且没有定义foo.o所引用的其他符号 。
  • b.o定义符号B

然后: -

  • foo.o,对象文件必须链接。链接器将 定义A和未解决的参考B添加到该程序。
  • libbar.a,链接器需要一个未解决的参考B的定义,所以它看起来在包里。
  • a.o未定义B或任何其他未解决的符号。它没有链接。没有添加A的第二个定义。
  • b.o定义B,所以它是链接的。将B的定义添加到程序中。
  • 链接器继续。

程序中不需要两个定义为A的对象文件。没有 多重定义错误。

案例3输入文件包含[foo.o ... libbar.a]。

  • libbar.a包含目标文件a.ob.o
  • foo.o定义符号A。它引用但不定义符号BC
  • a.o还定义了A,它定义了B,并且没有定义foo.o所引用的其他符号 。
  • b.o定义符号C

然后: -

  • foo.o,对象文件链接。链接程序向程序添加了A的定义以及未解决的对BC的引用。
  • libbar.a,链接器需要定义未解决的参考BC所以它看起来在包里。
  • a.o没有定义C。但它确定了B。因此a.o已链接。这增加了所需的定义B,加上不需要的盈余定义A

这是一个多重定义错误。两个链接器都检测到它。联动结束。

多个定义错误,如果某些符号且仅当两个定义 被包含在在程序链接对象文件。来自静态库的对象文件仅链接以提供程序引用的符号的定义。如果存在 多重定义错误,则两个链接器都会检测到它。

那么为什么GNU链接器选项--whole-archive会给出不同的结果呢?

假设libbar.a包含a.ob.o。然后:

foo.o --whole-archive -lbar 

告诉给所有的目标文件中libbar.a是否 是需要或不链接链接。所以联动命令的该片段是简单地等效 到:

foo.o a.o b.o 

因此,在情况下上述2,加入--whole-archive创建多个定义错误那里是的方式没有它。不是 一种方式检测一个多重定义错误,没有检测到没有 它。

如果--whole-archive被错误地作为一方式“检测”虚拟 多个定义错误,则在将连接仍然 成功的情况下,它也是添加的冗余代码 到无限量的方式程序。 Mach-O链接器的选项-all_load也是如此。

不满意?

即使所有这些都是明确的,也许你依然留恋一些方法,使之 一个错误,当你连接的输入对象文件定义了 处还在于不需要通过另一个对象文件中定义的符号链接但是 碰巧被包含在一些输入静态库中。

嗯,这可能是你想了解的情况,但它只是 没有任何一种链接错误,多定义或以其他方式。链接中静态库的目的 是为您在输入对象文件中未定义的符号 提供默认定义。在目标文件中提供自己的定义 ,并且将忽略该库的默认值。

如果你不想联动这样的工作 - 它的目的是工作的方式 - 但是: -

  • 您仍想使用一个静态库
  • 你不想来自输入对象文件的任何定义优先于 一个位于静态库成员中的定义
  • 您不想链接任何冗余对象文件。

那么最简单的解决方案(虽然不一定是最耗时的生成时) 是这样的:

在你的项目构建提取静态库的所有成员为纽带的 先决条件步的方式,也让你的 他们的名字列表,例如:

$ LIBFOOBAR_OBJS=`ar xv libfoobar.a | sed 's/x - //'g` 
$ echo $LIBFOOBAR_OBJS 
foo.o bar.o 

(但提取他们某处,他们不能揍你构建任何目标文件)。然后,再次在链接步骤之前,运行初步丢弃 链接,其中$LIBFOOBAR_OBJS替换libfoobar.a。的E.g 代替

cc -o prog x.o y.o z.o ... -lfoobar ... 

运行

cc -o deleteme x.o y.o z.o ... $LIBFOOBAR_OBJS ... 

如果初步联动失败 - 有多重定义错误或 别的 - 然后停在那里。否则,继续与真正的联系。 您将不会链接prog中的任何冗余对象文件。除非它不能与多 定义错误

在专业实践的价格正在执行 的deleteme一个联动装置是多余的,没有人运行构建这样的,以阻止所述 远程可能性,即程序员定义了一个功能 x.o y.o z.o其中一个敲除 libfoobar.a成员中定义的函数,而没有意义到。能力和代码审查是 指望避免这一点,以相同的方式,以避免 程序员定义x.o y.o z.o中的函数做任何事情, 应该使用库资源完成。


[1]而不是从静态 库在一次性使用联动提取所有的目标文件,你可以考虑使用一个--whole-archive暴殄天物 联动,与GNU链接, 或-all_load,与Mach-O连接器。但这种方法有潜在的缺陷,我不会在这里深入研究。

+0

壮观的答案,谢谢! – moof2k