我知道如何在使用 C 模板時使用 inline 關鍵字來避免“多重定義”。但是,我很好奇的是,聯結器如何區分哪個專業化是完全專業化并違反 ODR 和報告錯誤,而另一個專業化是隱式的并正確處理它?
從nm輸出中,我們可以在 main.o 和 other.o 中看到 int-version max() 和 char-version max() 的重復定義,但 C 聯結器僅報告“char-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 generic\n";
return (b<a)?a:b;
}
// 'multiple definition' link error if without inline
template<>
inline char max(char a, char b)
{
std::cout << "match full specialization\n";
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
I dumped my .o object file and found no many clues (maybe I am not quite familiar with object format spec.).
$ 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)
==== Test on Ubuntu ====
This is what I have got on my Ubuntu with g -9 after having remove inline from tmplhdr.hpp
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()' is marked with T which is not allowed to have multiple definitions; but 'in-version max()' is marked as W which allows multiple definitions. However, I start to be curious why nm gives different marks on Cygwin than on Ubuntu?? and Why linker on Cgywin can handle two T definitions correctly?
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)
uj5u.com熱心網友回復:
但是,我開始好奇為什么 nm 在 Cygwin 上的評分與在 Ubuntu 上的不同?為什么 Cgywin 上的聯結器可以正確處理兩個 T 定義?
您需要了解nm輸出并未為您提供全貌。
nm是 binutils 的一部分,并且使用libbfd. 其作業方式是將各種目標檔案格式決議為libbfd內部表示,然后使用諸如nm以人類可讀格式列印該內部表示的工具。
有些東西會“在翻譯中丟失”。這就是你不應該使用例如objdump查看ELF檔案的原因(至少不是在檔案的符號表中ELF)。
正如您正確推斷的那樣,max<int>()Linux 上允許使用多個符號的原因是編譯器將它們作為W(弱定義)符號發出。
對于 Windows 也是如此,除了Windows 使用較舊的COFF格式,它沒有弱符號。相反,符號被發送到一個特殊的.linkonce.$name部分,聯結器知道它可以選擇任何這樣的部分進入鏈接,但只應該這樣做一次(即它知道丟棄任何其他目標檔案中該部分的所有其他重復項) .
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/447267.html
