为什么链接器不抱怨重复符号?
Why doesn't the linker complain of duplicate symbols?
我有一个dummy.hpp
#ifndef DUMMY
#define DUMMY
void dummy();
#endif
和一个dummy.cpp
#include <iostream>
void dummy() {
std::cerr << "dummy" << std::endl;
}
和一个使用 dummy()
的 main.cpp
#include "dummy.hpp"
int main(){
dummy();
return 0;
}
然后我把dummy.cpp编译成三个库,libdummy1.a
,libdummy2.a
,libdummy.so
:
g++ -c -fPIC dummy.cpp
ar rvs libdummy1.a dummy.o
ar rvs libdummy2.a dummy.o
g++ -shared -fPIC -o libdummy.so dummy.cpp
当我尝试编译 main 和 link 虚拟库时
g++ -o main main.cpp -L. -ldummy1 -ldummy2
linker 没有产生重复符号错误。当我 link 静态地使用两个相同的库时,为什么会发生这种情况?
当我尝试
g++ -o main main.cpp -L. -ldummy1 -ldummy
也没有重复符号错误,为什么?
加载程序似乎总是选择动态库而不是 .o
文件中编译的代码。
如果 .so
文件既在 .a
文件中又在 .so
文件中,是否意味着总是从 .so
文件中加载相同的符号?
这是否意味着静态库中的静态符号table中的符号永远不会与.so
文件中的动态符号table中的符号冲突?
场景 1(双静态库)或场景 2(静态和共享库)都没有错误,因为 linker 从静态库或第一个共享库中获取第一个目标文件,它遇到的提供了它尚未定义的符号的定义。它只是忽略了同一符号的任何后续定义,因为它已经有了一个好的定义。一般而言,linker 只从库中获取它需要的东西。对于静态库,这是绝对正确的。对于共享库,如果满足任何缺失的符号,则共享库中的所有符号都可用;对于某些 link 用户,共享库的符号可能无论如何都可用,但其他版本仅在共享库提供至少一个定义时才记录使用共享库。
这也是您需要 link 库在目标文件之后的原因。您可以将 dummy.o
添加到 linking 命令中,只要它出现在库之前,就没有问题。在库之后添加 dummy.o
文件,你会得到双重定义的符号错误。
唯一一次你 运行 遇到这种双重定义的问题是,如果库 1 中有一个目标文件同时定义了 dummy
和 extra
,并且库 1 中有一个目标文件定义了 dummy
和 alternative
的库 2,并且代码需要 extra
和 alternative
的定义——然后你有 dummy
的重复定义,这会导致麻烦。实际上,目标文件可能位于单个库中,这会造成麻烦。
考虑:
/* file1.h */
extern void dummy();
extern int extra(int);
/* file1.cpp */
#include "file1.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int extra(int i) { return i + 37; }
/* file2.h */
extern void dummy();
extern int alternative(int);
/* file2.cpp */
#include "file2.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int alternative(int i) { return -i; }
/* main.cpp */
#include "file1.h"
#include "file2.h"
int main()
{
return extra(alternative(54));
}
由于 dummy
的双重定义,您将无法 link 显示的三个源文件中的目标文件,即使主代码没有调用 [=23] =].
关于:
The loader seems always to choose dynamic libs and not compiled in the .o files.
否; linker 总是试图无条件地加载目标文件。它在命令行上遇到库时扫描它们,收集它需要的定义。如果目标文件在库之前,则没有问题,除非两个目标文件定义了相同的符号('one definition rule' 响铃了吗?)。如果某些目标文件跟在库之后,如果库定义了后面的目标文件定义的符号,您可能会 运行 发生冲突。请注意,当它开始时,linker 正在寻找 main
的定义。它从被告知的每个目标文件中收集已定义的符号和引用的符号,并不断添加代码(从库中)直到定义了所有引用的符号。
Does it means the same symbol is always loaded from .so
file, if it is both in .a
and .so
file?
否;这取决于先遇到哪个。如果首先遇到 .a
,则 .o
文件会从库中有效地复制到可执行文件中(共享库中的符号将被忽略,因为可执行文件中已经有它的定义)。如果先遇到 .so
,.a
中的定义将被忽略,因为 linker 不再寻找该符号的定义——它已经有了。
Does it mean that symbols in static symbol table in a static library are never in conflict with those in dynamic symbol table in .so
file?
您可能会遇到冲突,但遇到的第一个定义会解析 linker 的符号。如果满足引用的代码通过定义所需的其他符号引起冲突,它只会 运行 发生冲突。
If I link 2 shared libs, can I get conflicts and the link phase failed?
正如我在评论中指出的那样:
My immediate reaction is "Yes, you can". It would depend on the content of the two shared libraries, but you could run into problems, I believe. […cogitation…] How would you show this problem? … It's not as easy as it seems at first sight. What is required to demonstrate such a problem? … Or am I overthinking this? … […time to go play with some sample code…]
经过一些实验,我的临时经验答案是 "No, you can't"(或 "No, on at least some systems, you don't run into a conflict")。我很高兴我搪塞了。
使用上面显示的代码(2 个头文件,3 个源文件)和 运行 GCC 5.3.0 on Mac OS X 10.10.5 (Yosemite ), 我可以 运行:
$ g++ -O -c main.cpp
$ g++ -O -c file1.cpp
$ g++ -O -c file2.cpp
$ g++ -shared -o libfile2.so file2.o
$ g++ -shared -o libfile1.so file1.o
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
$ echo $?
239
$ otool -L test2
test2:
libfile2.so (compatibility version 0.0.0, current version 0.0.0)
libfile1.so (compatibility version 0.0.0, current version 0.0.0)
/opt/gcc/v5.3.0/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.21.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
/opt/gcc/v5.3.0/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
$
在Mac OS X 上使用.so
作为扩展名是一种惯例(通常是.dylib
),但似乎可行。
然后我修改了 .cpp
文件中的代码,以便 extra()
在 return
之前调用 dummy()
,alternative()
和 main()
。重新编译和重建共享库后,我 运行 程序。第一行输出来自 main()
调用的 dummy()
。然后你得到另外两行由 alternative()
和 extra()
产生的顺序,因为 return extra(alternative(54));
的调用序列要求。
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
dummy() from file1.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$ g++ -o test2 main.o -L. -lfile2 -lfile1
$ ./test2
dummy() from file2.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$
请注意,main()
调用的函数是第一个出现在 link 所用的库中的函数。但是(至少在 Mac OS X 10.10.5 上)linker 不会 运行 发生冲突。但是请注意,每个共享对象中的代码都调用 dummy()
的 'its own' 版本——两个共享库之间对于哪个函数是 dummy()
存在分歧。 (如果 dummy()
函数位于共享库中单独的对象文件中会很有趣;那么 dummy()
的哪个版本被调用?)但是在显示的极其简单的场景中, main()
函数设法只调用 dummy()
函数之一。 (请注意,如果发现平台之间的这种行为存在差异,我不会感到惊讶。我已经确定了我测试代码的位置。如果您在某些平台上发现不同的行为,请告诉我。)
我有一个dummy.hpp
#ifndef DUMMY
#define DUMMY
void dummy();
#endif
和一个dummy.cpp
#include <iostream>
void dummy() {
std::cerr << "dummy" << std::endl;
}
和一个使用 dummy()
的 main.cpp#include "dummy.hpp"
int main(){
dummy();
return 0;
}
然后我把dummy.cpp编译成三个库,libdummy1.a
,libdummy2.a
,libdummy.so
:
g++ -c -fPIC dummy.cpp
ar rvs libdummy1.a dummy.o
ar rvs libdummy2.a dummy.o
g++ -shared -fPIC -o libdummy.so dummy.cpp
当我尝试编译 main 和 link 虚拟库时
g++ -o main main.cpp -L. -ldummy1 -ldummy2
linker 没有产生重复符号错误。当我 link 静态地使用两个相同的库时,为什么会发生这种情况?
当我尝试
g++ -o main main.cpp -L. -ldummy1 -ldummy
也没有重复符号错误,为什么?
加载程序似乎总是选择动态库而不是 .o
文件中编译的代码。
如果 .so
文件既在 .a
文件中又在 .so
文件中,是否意味着总是从 .so
文件中加载相同的符号?
这是否意味着静态库中的静态符号table中的符号永远不会与.so
文件中的动态符号table中的符号冲突?
场景 1(双静态库)或场景 2(静态和共享库)都没有错误,因为 linker 从静态库或第一个共享库中获取第一个目标文件,它遇到的提供了它尚未定义的符号的定义。它只是忽略了同一符号的任何后续定义,因为它已经有了一个好的定义。一般而言,linker 只从库中获取它需要的东西。对于静态库,这是绝对正确的。对于共享库,如果满足任何缺失的符号,则共享库中的所有符号都可用;对于某些 link 用户,共享库的符号可能无论如何都可用,但其他版本仅在共享库提供至少一个定义时才记录使用共享库。
这也是您需要 link 库在目标文件之后的原因。您可以将 dummy.o
添加到 linking 命令中,只要它出现在库之前,就没有问题。在库之后添加 dummy.o
文件,你会得到双重定义的符号错误。
唯一一次你 运行 遇到这种双重定义的问题是,如果库 1 中有一个目标文件同时定义了 dummy
和 extra
,并且库 1 中有一个目标文件定义了 dummy
和 alternative
的库 2,并且代码需要 extra
和 alternative
的定义——然后你有 dummy
的重复定义,这会导致麻烦。实际上,目标文件可能位于单个库中,这会造成麻烦。
考虑:
/* file1.h */
extern void dummy();
extern int extra(int);
/* file1.cpp */
#include "file1.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int extra(int i) { return i + 37; }
/* file2.h */
extern void dummy();
extern int alternative(int);
/* file2.cpp */
#include "file2.h"
#include <iostream>
void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
int alternative(int i) { return -i; }
/* main.cpp */
#include "file1.h"
#include "file2.h"
int main()
{
return extra(alternative(54));
}
由于 dummy
的双重定义,您将无法 link 显示的三个源文件中的目标文件,即使主代码没有调用 [=23] =].
关于:
The loader seems always to choose dynamic libs and not compiled in the .o files.
否; linker 总是试图无条件地加载目标文件。它在命令行上遇到库时扫描它们,收集它需要的定义。如果目标文件在库之前,则没有问题,除非两个目标文件定义了相同的符号('one definition rule' 响铃了吗?)。如果某些目标文件跟在库之后,如果库定义了后面的目标文件定义的符号,您可能会 运行 发生冲突。请注意,当它开始时,linker 正在寻找 main
的定义。它从被告知的每个目标文件中收集已定义的符号和引用的符号,并不断添加代码(从库中)直到定义了所有引用的符号。
Does it means the same symbol is always loaded from
.so
file, if it is both in.a
and.so
file?
否;这取决于先遇到哪个。如果首先遇到 .a
,则 .o
文件会从库中有效地复制到可执行文件中(共享库中的符号将被忽略,因为可执行文件中已经有它的定义)。如果先遇到 .so
,.a
中的定义将被忽略,因为 linker 不再寻找该符号的定义——它已经有了。
Does it mean that symbols in static symbol table in a static library are never in conflict with those in dynamic symbol table in
.so
file?
您可能会遇到冲突,但遇到的第一个定义会解析 linker 的符号。如果满足引用的代码通过定义所需的其他符号引起冲突,它只会 运行 发生冲突。
If I link 2 shared libs, can I get conflicts and the link phase failed?
正如我在评论中指出的那样:
My immediate reaction is "Yes, you can". It would depend on the content of the two shared libraries, but you could run into problems, I believe. […cogitation…] How would you show this problem? … It's not as easy as it seems at first sight. What is required to demonstrate such a problem? … Or am I overthinking this? … […time to go play with some sample code…]
经过一些实验,我的临时经验答案是 "No, you can't"(或 "No, on at least some systems, you don't run into a conflict")。我很高兴我搪塞了。
使用上面显示的代码(2 个头文件,3 个源文件)和 运行 GCC 5.3.0 on Mac OS X 10.10.5 (Yosemite ), 我可以 运行:
$ g++ -O -c main.cpp
$ g++ -O -c file1.cpp
$ g++ -O -c file2.cpp
$ g++ -shared -o libfile2.so file2.o
$ g++ -shared -o libfile1.so file1.o
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
$ echo $?
239
$ otool -L test2
test2:
libfile2.so (compatibility version 0.0.0, current version 0.0.0)
libfile1.so (compatibility version 0.0.0, current version 0.0.0)
/opt/gcc/v5.3.0/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.21.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
/opt/gcc/v5.3.0/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
$
在Mac OS X 上使用.so
作为扩展名是一种惯例(通常是.dylib
),但似乎可行。
然后我修改了 .cpp
文件中的代码,以便 extra()
在 return
之前调用 dummy()
,alternative()
和 main()
。重新编译和重建共享库后,我 运行 程序。第一行输出来自 main()
调用的 dummy()
。然后你得到另外两行由 alternative()
和 extra()
产生的顺序,因为 return extra(alternative(54));
的调用序列要求。
$ g++ -o test2 main.o -L. -lfile1 -lfile2
$ ./test2
dummy() from file1.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$ g++ -o test2 main.o -L. -lfile2 -lfile1
$ ./test2
dummy() from file2.cpp
dummy() from file2.cpp
dummy() from file1.cpp
$
请注意,main()
调用的函数是第一个出现在 link 所用的库中的函数。但是(至少在 Mac OS X 10.10.5 上)linker 不会 运行 发生冲突。但是请注意,每个共享对象中的代码都调用 dummy()
的 'its own' 版本——两个共享库之间对于哪个函数是 dummy()
存在分歧。 (如果 dummy()
函数位于共享库中单独的对象文件中会很有趣;那么 dummy()
的哪个版本被调用?)但是在显示的极其简单的场景中, main()
函数设法只调用 dummy()
函数之一。 (请注意,如果发现平台之间的这种行为存在差异,我不会感到惊讶。我已经确定了我测试代码的位置。如果您在某些平台上发现不同的行为,请告诉我。)