我知道如何在使用C++模板时使用内联关键字来避免"多重定义"。然而,我很好奇的是,链接器是如何区分哪个专业化是完全专业化并违反ODR并报告错误的,而另一个专业化是隐含的并正确处理它的?
从nm
输出中,我们可以在main.o和other.o中看到int version max((和char version max(?链接器是如何区分它们的?
// tmplhdr.hpp
#include <iostream>
// this function is instantiated in main.o and other.o
// but leads no 'multiple definition' error by linker
template<typename T>
T max(T a, T b)
{
std::cout << "match genericn";
return (b<a)?a:b;
}
// 'multiple definition' link error if without inline
template<>
inline char max(char a, char b)
{
std::cout << "match full specializationn";
return (b<a)?a:b;
}
// main.cpp
#include "tmplhdr.hpp"
extern int mymax(int, int);
int main()
{
std::cout << max(1,2) << std::endl;
std::cout << mymax(10,20) << std::endl;
std::cout << max('a','b') << std::endl;
return 0;
}
// other.cpp
#include "tmplhdr.hpp"
int mymax(int a, int b)
{
return max(a, b);
}
Ubuntu上的测试输出是合理的;但Cygwin的输出相当奇怪和令人困惑。。。
===Cygwin测试===
g++链接器仅报告"char max(char,char("重复。
$ g++ -o main.exe main.cpp other.cpp
/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld:
/tmp/ccYivs3O.o:other.cpp:(.text$_Z3maxIcET_S0_S0_[_Z3maxIcET_S0_S0_]+0x0):
multiple definition of `char max<char>(char, char)';
/tmp/cc7HJqbS.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
我丢弃了我的.o对象文件,没有发现太多线索(也许我不太熟悉对象格式规范(
$ nm main.o | grep max | c++filt.exe
0000000000000000 p .pdata$_Z3maxIcET_S0_S0_
0000000000000000 p .pdata$_Z3maxIiET_S0_S0_
0000000000000000 t .text$_Z3maxIcET_S0_S0_
0000000000000000 t .text$_Z3maxIiET_S0_S0_
0000000000000000 r .xdata$_Z3maxIcET_S0_S0_
0000000000000000 r .xdata$_Z3maxIiET_S0_S0_
0000000000000000 T char max<char>(char, char) <-- full specialization
0000000000000000 T int max<int>(int, int) <<-- implicit specialization
U mymax(int, int)
$ nm other.o | grep max | c++filt.exe
0000000000000000 p .pdata$_Z3maxIcET_S0_S0_
0000000000000000 p .pdata$_Z3maxIiET_S0_S0_
0000000000000000 t .text$_Z3maxIcET_S0_S0_
0000000000000000 t .text$_Z3maxIiET_S0_S0_
0000000000000000 r .xdata$_Z3maxIcET_S0_S0_
0000000000000000 r .xdata$_Z3maxIiET_S0_S0_
000000000000009b t _GLOBAL__sub_I__Z5mymaxii
0000000000000000 T char max<char>(char, char) <-- full specialization
0000000000000000 T int max<int>(int, int) <-- implicit specialization
0000000000000000 T mymax(int, int)
===在Ubuntu上测试===
这是我在从tmplhdr.hpp
中删除inline
后,在我的Ubuntu上用g++-9得到的
tony@Win10Bedroom:/mnt/c/Users/Tony Su/My Documents/cpphome$ g++ -o main main.o other.o
/usr/bin/ld: other.o: in function `char max<char>(char, char)':
other.cpp:(.text+0x0): multiple definition of `char max<char>(char, char)'; main.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
"char version max(("标记有T
,不允许有多个定义;但"在版本max((中"被标记为W
,这允许多个定义。然而,我开始好奇为什么nm
在Cygwin上的评分与在Ubuntu上的评分不同??以及为什么Cgywin上的链接器可以正确处理两个T
定义?
tony@Win10Bedroom:/mnt/c/Users/Tony Su/My Documents/cpphome$ nm main.o | grep max | c++filt
0000000000000133 t _GLOBAL__sub_I__Z3maxIcET_S0_S0_
0000000000000000 T char max<char>(char, char)
0000000000000000 W int max<int>(int, int)
U mymax(int, int)
tony@Win10Bedroom:/mnt/c/Users/Tony Su/My Documents/cpphome$ nm other.o | grep max | c++filt
00000000000000d7 t _GLOBAL__sub_I__Z3maxIcET_S0_S0_
0000000000000000 T char max<char>(char, char)
0000000000000000 W int max<int>(int, int)
000000000000003e T mymax(int, int)
然而,我开始好奇为什么nm在Cygwin上的标记与在Ubuntu上的不同??为什么Cgywin上的链接器可以正确处理两个T定义?
您需要了解nm
输出并不能为您提供完整的信息。
nm
是binutils的一部分,并且使用libbfd
。其工作方式是将各种对象文件格式解析为libbfd
内部表示,然后像nm
这样的工具以人类可读的格式打印该内部表示。
有些事情得到了";翻译中丢失";。这就是为什么您不应该使用例如objdump
来查看ELF
文件(至少不要查看ELF
文件的符号表(。
正如您正确推断的那样,Linux上允许多个max<int>()
符号的原因是编译器将它们作为W
(弱定义(符号发出。
Windows也是如此,除了Windows使用较旧的COFF
格式,该格式没有弱符号。相反,符号被发送到一个特殊的.linkonce.$name
节中,链接器知道它可以在链接中选择任何这样的节,但应该只选择一次(即,它知道在任何其他对象文件中丢弃该节的所有其他副本(。