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

moo*_*f2k 3 xcode darwin clang ld

这个问题以前曾针对gcc提出过,但达尔文的 ld (clang?)似乎以不同的方式处理这个问题。

假设我main()在两个文件 main1.cc 和 main2.cc 中定义了一个函数。如果我尝试将它们一起编译,我将得到(所需的)重复符号错误:

$ 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)
Run Code Online (Sandbox Code Playgroud)

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

$ ar rcs libmain1.a main1.o
$ g++ -o main libmain1.a main2.o
(no error)
Run Code Online (Sandbox Code Playgroud)

使用 gcc,您可以使用 lib 包装--whole-archive,然后 gcc 的 ld 将产生错误。此选项对于随 xcode 一起提供的 ld 不可用。

是否可以让ld打印错误?

Mik*_*han 5

我确信您知道不应该将包含main函数的目标文件放入静态库中。如果我们的读者不这样做:库用于包含可以被许多程序重用的函数。一个程序只能包含一个 main函数,并且该程序的函数作为 另一个程序的函数main被重用的可能性可以忽略不计。main所以main函数不会进入库中。(这条规则有一些奇怪的例外)。

接下来说说你担心的问题。为简单起见,我将在其余部分中排除共享/动态库的链接。

当竞争定义位于不同的输入对象文件中时,链接器会检测到链接中的重复符号错误(也称为多重定义错误),但当一个定义是输入对象文件而另一个定义位于输入静态库中时,链接器不会检测到该错误。在这种情况下,如果在静态库之前传递了选项,GNU 链接器可以检测到多重定义的符号。--whole-archive但是您的链接器Darwin Mach-O 链接器没有该选项。

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

  • 在 [ ... ] 的情况下,链接中确实存在多重定义错误。foo.obar.o

  • 在 [ ... ] 情况下,链接中确实存在多重定义错误。foo.olibbar.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.o定义bar.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
Run Code Online (Sandbox Code Playgroud)

告诉链接器链接所有目标文件,libbar.a无论它们是否需要。所以这个链接命令片段就相当于:

foo.o a.o b.o
Run Code Online (Sandbox Code Playgroud)

因此,在上面的情况 2中,添加--whole-archive是一种创建多重定义错误的方法 ,而没有它则不会出现多重定义错误。不是一种检测多重定义错误的方法,如果没有它就无法检测到。

如果--whole-archive错误地将其用作“检测”虚构的多重定义错误的方式,那么在链接仍然成功的情况下,它也是向程序添加无限量冗余代码的一种方式。-all_loadMach-O 链接器的选项也是如此。

不满意?

即使所有这些都清楚了,当链接中的输入对象文件定义了一个符号,该符号也在链接不需要但恰好包含在另一个对象文件中定义时,也许您仍然渴望某种方式使其成为错误在一些输入静态库中。

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

如果您不希望链接像那样工作 - 它的预期工作方式 - 但是:-

  • 你仍然想使用静态库
  • 您不希望输入对象文件中的任何定义优先于静态库成员中的定义
  • 您不想链接任何冗余的目标文件。

那么最简单的解决方案(尽管不一定是构建时最耗时的)是这样的:

在您的项目构建中,提取静态库的所有成员作为链接步骤的先决条件,同时还提供其名称列表,例如:

$ LIBFOOBAR_OBJS=`ar xv libfoobar.a | sed 's/x - //'g`
$ echo $LIBFOOBAR_OBJS
foo.o bar.o
Run Code Online (Sandbox Code Playgroud)

(但是将它们提取到无法破坏您构建的任何目标文件的地方)。然后,再次在链接步骤之前,运行初步的废弃链接,其中$LIBFOOBAR_OBJS替换libfoobar.a。例如代替

cc -o prog x.o y.o z.o ... -lfoobar ...
Run Code Online (Sandbox Code Playgroud)

跑步

cc -o deleteme x.o y.o z.o ... $LIBFOOBAR_OBJS ...
Run Code Online (Sandbox Code Playgroud)

如果初步链接失败(出现多重定义错误或其他任何情况),则在此停止。否则继续进行真正的链接。您不会在prog. 价格执行的链接deleteme是多余的,除非它因多重定义错误而失败1

在专业实践中,没有人运行这样的构建来阻止程序员在其中定义了一个函数而无意中x.o y.o z.o淘汰了在 成员中定义的函数 的可能性。能力和代码审查可以避免这种情况,就像它们可以避免程序员定义函数来执行任何应该使用库资源完成的操作一样。libfoobar.a x.o y.o z.o


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