之前已经问过gcc这个问题,但Darwin的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)
但是,如果我将其中一个粘贴到静态库中,当我去链接应用程序时,我不会收到错误:
$ ar rcs libmain1.a main1.o
$ g++ -o main libmain1.a main2.o
(no error)
使用gcc,您可以使用--whole-archive
包装lib,然后gcc' s ld将产生错误。对于带有xcode的ld,此选项不可用。
是否可以让ld打印错误?
答案 0 :(得分:2)
我确定你知道你不应该放一个目标文件
在静态库中包含main
函数。万一我们的读者
不包含:库用于包含可能被许多程序重用的函数。
一个程序只能包含一个main
函数和可能性
可以忽略的是,main
程序的main
函数可以重用为main
另一个人的功能。所以--whole-archive
函数不会进入图书馆。 (这个规则有一些奇怪的例外)。
然后解决你担心的问题。为简单起见 我将在其余部分中排除共享/动态库的链接。
您的链接器检测到重复的符号错误(a.k.a.多个定义错误)
在竞争定义位于不同输入对象文件中时的链接
但是当一个定义是输入对象文件而另一个定义是输入对象文件时,它不会检测到它
在输入静态库中。在那种情况下,GNU链接器可以检测
如果之前传递了--whole-archive
选项,则为多重定义的符号
静态库。但你的链接器Darwin Mach-O linker,
没有这个选择。
请注意,虽然您的链接器不支持-all_load
,但它有一个
等效选项foo.o
。但不要随便逃跑,因为无论如何,这种担忧都是毫无根据的。对于两个连接器:
[bar.o
中的链接确实 是多重定义错误...
foo.o
]案例。
[libbar.a
... foo.o
]案例中的链接确实存在不多重定义错误。
此外还有GNU链接器:
--whole-archive libbar.a
... foo.o
]案例。在任何情况下,任何一个链接器都不允许对符号进行多次定义 进入你的程序未检测到并随意使用其中一个。
关联libfoo.o
和关联foo.o
之间的区别是什么?
链接器只会将目标文件添加到您的程序中。
更确切地说,当它遇到输入文件foo.o
时,它会添加到您的程序中
来自libfoo.a
的所有符号引用和符号定义。 (对于初学者
至少:如果您已经要求,最终可能会丢弃未使用的定义,
如果它可以这样做而不会附带地丢弃任何用过的东西。)
静态库只是一包目标文件。链接器遇到输入文件时
libfoo.a
,默认情况下,它不会将任何对象文件添加到包中
你的计划。
只会在链接的那一点检查包的内容。
如果已添加,必须检查行李的内容 某些符号引用您的程序没有定义。那些 未解析的符号可能会在包中的某些目标文件中定义。
如果必须查看包,那么它将检查目标文件 看看它们中是否有任何已包含的未解析符号的定义 程序。如果有任何这样的目标文件,那么它会将它们添加到程序中并重新考虑它是否需要继续查看。当它找不到程序需要的更多目标文件或找到程序引用的所有符号的定义时(以先到者为准),它会停止查看。
如果需要包中的任何目标文件,则至少增加一个符号
程序的定义,以及可能更多未解决的符号。然后链接器继续。
一旦它遇到foo.o
并考虑了你的程序所需的那个包中的哪个(如果有的话)目标文件,
它不会再次考虑它,除非它再次遇到,,后来在链接中
序列
<强>因此... 强>
案例1 。输入文件包含[bar.o
... foo.o
]。 bar.o
和A
定义符号A
。必须链接两个目标文件,因此foo.o
的两个定义都必须
被添加到程序中,这是一个多重定义错误。两个连接器都检测到它。
案例2 输入文件包含[libbar.a
... libbar.a
]。
a.o
包含目标文件b.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
的定义已添加到程序中。程序中不需要两个定义foo.o
的目标文件。没有
多重定义错误。
案例3 输入文件包含[libbar.a
... libbar.a
]。
a.o
包含目标文件b.o
和foo.o
。A
定义了符号B
。它引用但未定义符号C
和a.o
。A
还定义了B
,它定义了foo.o
,并且没有定义其他符号
由b.o
引用的。C
定义了符号foo.o
。然后: -
A
处,目标文件已链接。链接器向程序添加B
的定义以及对C
和libbar.a
的未解析引用。B
,链接器需要未解析引用的定义C
和a.o
所以它看起来像是在包里。C
未定义B
。但它确实定义了a.o
。所以B
是相互关联的。这增加了A
的必需定义,加上--whole-archive
的不需要的剩余定义。这是一个多重定义错误。两个连接器都检测到它。联系结束。
当且仅当两个定义时, 是多重定义错误 某些符号包含在程序中链接的目标文件中。静态库中的目标文件仅链接以提供程序引用的符号的定义。如果有 多重定义错误,然后两个连接器都检测到它。
那么为什么GNU链接器选项libbar.a
会产生不同的结果呢?
假设a.o
包含b.o
和foo.o --whole-archive -lbar
。然后:
libbar.a
告诉链接器链接foo.o a.o b.o
中的所有目标文件是否
他们需要与否。所以这个连接命令的片段就是等价的
到:
--whole-archive
因此,在 case 2 中,添加--whole-archive
是一种方法
创建多重定义错误,其中没有 none 。不
检测未经检测到的多重定义错误的方法
它
如果-all_load
被错误地用作一种方式&#34;检测&#34;虚拟
多个定义错误,然后在那些链接的情况下
成功之后,它也是添加无限量冗余代码的一种方式
到程序。 Mach-O链接器的$ LIBFOOBAR_OBJS=`ar xv libfoobar.a | sed 's/x - //'g`
$ echo $LIBFOOBAR_OBJS
foo.o bar.o
选项也是如此。
不满意?
即使一切都很清楚,也许你仍然渴望某种方式来实现它 链接中的输入对象文件定义符号时出错 也可以在链接不需要的另一个目标文件中定义 碰巧包含在一些输入静态库中。
嗯,这可能是你想知道的情况,但它只是 不是任何链接错误,多重定义或其他类型。 目的 链接中的静态库是为了提供符号的默认定义 您没有在输入对象文件中定义。提供您自己的定义 在目标文件中,将忽略libary默认值。
如果你不希望联系工作那样 - 它的工作方式 - 但是: -
然后是最简单的解决方案(虽然在构建时不一定是最耗时的) 是这样的:
在您的项目构建中,将静态库的所有成员解压缩为 链接步骤的先决条件,也给出了列表 他们的名字,例如:
$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 ...
运行
prog
如果初步连接失败 - 出现多重定义错误或
别的什么 - 然后停在那里。否则继续进行真正的联系。
您无法链接deleteme
中的任何冗余目标文件。价格正在上涨
x.o y.o z.o
的链接是多余的,除非它以多重失败
定义错误 1
在专业实践中,没有人会运行这样的构建来阻止它
程序员定义函数的远程可能性
libfoobar.a
中的一个敲除了成员中定义的函数
<{1}} 没有意义。能力和代码审查是
依靠以避免这种情况,以同样的方式指望他们避免
程序员在x.o y.o z.o
中定义一个函数来做任何事情
应该使用库资源来完成。
<小时/> [1]而不是从静态中提取所有目标文件 用于丢弃链接的库,你可以考虑一个 使用
--whole-archive
与GNU链接器的一次性链接,
或者-all_load
,使用Mach-O链接器。但是存在潜在的陷阱
这种方法我不会钻研到这里。