xcode ld 检测静态库中的重复符号

xcode ld detect duplicate symbol in static libraries

之前 gcc 已经问过这个问题,但达尔文的 ld(clang?)似乎以不同的方式处理这个问题。

假设我在两个文件 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)

但是如果我将其中一个粘贴到静态库中,当我转到 link 应用程序时我不会收到错误:

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

使用 gcc,您可以用 --whole-archive 包装 lib,然后 gcc 的 ld 将产生错误。此选项不适用于带有 xcode.

的 ld

是否可以让 ld 打印错误?

我相信你知道你不应该把目标文件 在静态库中包含一个 main 函数。如果我们的任何读者 不是:库用于包含可以被许多程序重用的函数。 一个程序只能包含一个 main 函数,并且似然 程序的 main 函数可以作为 main 重用,这一点可以忽略不计 另一个的功能。所以 main 函数不在库中。 (此规则有一些奇怪的例外)。

接着说你担心的问题。为简单起见, 我将排除 linkage of shared/dynamic 库的剩余部分。

您的 linker 检测到重复符号错误(a.k.a。多重定义错误) 在竞争定义位于不同输入目标文件中的 link 时代 但当一个定义是输入目标文件而另一个定义时未检测到它 在输入静态库中。在那种情况下,GNU linker 可以检测 多次定义的符号,如果它在之前传递 --whole-archive 选项 静态库。但是你的link呃,Darwin Mach-O linker, 没有那个选项。

请注意,虽然您的 link 用户不支持 --whole-archive,但它有一个 等效选项 -all_load。但是不要 运行 就这么想了,反正这种担心是多余的。对于两个 link 用户:

  • 确实link年龄在[foo.o ... bar.o]案例.

  • 在 [foo.o ... [=26] linkage 中确实存在 not 多重定义错误=]] case.

另外对于 GNU linker:

  • 确实link年龄中的多重定义错误 [foo.o ... --whole-archive libbar.a] 案例。

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

linking foo.o和linking libfoo.o有什么区别?

linker 只会将 object files 添加到您的程序中。 更准确地说,当它遇到一个输入文件 foo.o 时,它会添加到您的程序中 foo.o 中的所有符号引用和符号定义。 (对于初学者 至少:如果您提出要求,它最终可能会丢弃未使用的定义, 并且如果它可以这样做而不附带丢弃任何用过的)。

静态库就是一堆目标文件。当 linker 遇到输入文件时 libfoo.a,默认不会将包中的any个目标文件添加到 你的程序。

它只会在 linkage.

必须检查包中的内容

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

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

如果需要包中的任何目标文件,这将至少添加一个符号 定义到您的程序,可能还有更多未解析的符号。然后 linker 继续。 一旦它遇到 libfoo.a 并考虑了你的程序需要那个包中的哪些目标文件,如果有的话, 它不会再考虑它,除非它再次,以后在link时代遇到它 顺序。

所以...

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

案例 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,目标文件必须 linked。 linker 添加了 A 的定义和对程序 B 的未解析引用。
  • libbar.a,linker 需要一个未解析引用的定义 B 所以它会在包中查找。
  • a.o 没有定义 B 或任何其他未解析的符号。它不是 linked。 A的第二个定义没有添加。
  • b.o 定义了 B,所以它是 linked。 B的定义添加到程序中。
  • link人继续。

程序中不需要同时定义 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,目标文件被 linked。 linker 将 A 的定义和对 BC.
  • 的未解析引用添加到程序中
  • libbar.a,linker 需要未解析引用的定义BC 所以它看起来在袋子里。
  • a.o 没有定义 C。但它确实定义了 B。所以 a.o 是 linked。这添加了 B 的必需定义,加上不需要的 surplus 定义 A.

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

当且仅当有两个定义时,多重定义错误 一些符号包含在目标文件 中,这些文件 link 在程序 中编辑。来自静态库的目标文件 linked 仅用于提供程序引用的符号定义。如果有 一个多重定义错误,然后两个 link 人都检测到它。

那么为什么 GNU linker 选项 --whole-archive 给出不同的结果?

假设 libbar.a 包含 a.ob.o。那么:

foo.o --whole-archive -lbar

告诉 linker link libbar.a 中的所有目标文件是否 它们是否需要。所以 linkage 命令的这个片段是完全等价的 至:

foo.o a.o b.o

因此在上面的案例2中,添加--whole-archive是一种方式 创建 一个多重定义错误,其中 none 没有它。不是 检测多重定义错误的方法 它。

并且如果--whole-archive被错误地用作"detecting"虚构的方式 多个定义错误,然后在 linkage 尽管如此的情况下 成功了,这也是添加无限量冗余代码的一种方式 到节目。 Mach-O linker 的 -all_load 选项也是如此。

不满意?

即使一切都清楚了,也许你仍然渴望某种方式来实现它 当您的 linkage 中的输入目标文件定义了一个符号时出现错误 也在 linkage 不需要的另一个目标文件中定义,但是 恰好包含在一些输入静态库中。

嗯,这可能是您想了解的情况,但它只是 不是任何类型的link年龄错误、多重定义或其他。 目的 linkage 中的静态库是提供符号的默认定义 您没有在输入目标文件中定义的。提供您自己的定义 在目标文件中,库默认值被忽略。

如果您不希望 linkage 像那样工作 - 它的工作方式 - 但是:-

  • 你还是想用静态库
  • 您不希望输入目标文件中的任何定义占上风 静态库成员中的一个
  • 您不想link任何多余的目标文件。

然后是最简单的解决方案(虽然不一定是构建时耗时最少的) 这是:

在您的项目构建中提取静态库的所有成员作为 link 步骤的先决条件,也为您提供列表 他们的名字,例如:

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

(但是将它们解压到它们不会破坏您构建的任何目标文件的地方)。然后,再次在 link 步骤之前, 运行 初步丢弃 link $LIBFOOBAR_OBJS 取代 libfoobar.a 的年龄。例如 而不是

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

运行

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

如果初步 linkage 失败 - 多重定义错误或 其他任何东西 - 然后停在那里。否则继续真正的link年龄。 您不会 link 在 prog 中有任何冗余目标文件。价格正在执行 一个 linkage of deleteme 是多余的,除非它因多次失败而失败 定义错误1

在专业实践中,没有人 运行s 像那样构建来阻止 程序员在其中定义函数的可能性很小 x.o y.o z.o 中的一个删除了在成员中定义的函数 libfoobar.a无意。能力和代码审查是 指望避免这种情况,就像他们指望避免这种情况一样 程序员在 x.o y.o z.o 中定义一个函数来做任何事情 应该使用图书馆资源来完成。


[1] 而不是从静态文件中提取所有目标文件 用于一次性 linkage 的库,您可以考虑 一次性 linkage 使用 --whole-archive,使用 GNU linker, 或 -all_load,使用 Mach-O linker。但是有潜在的陷阱 我不会在这里深入研究这种方法。