Я знаю, как использовать встроенное ключевое слово, чтобы избежать «множественного определения» при использовании шаблона С++. Однако мне любопытно, как компоновщик различает, какая специализация является полной специализацией и нарушает ODR и сообщает об ошибке, в то время как другая специализация является неявной и правильно обрабатывает ее?
Из вывода nm
мы видим дублированные определения в main.o и other.o как для int-версии max(), так и для char-версии max(), но компоновщик C++ сообщает только об «множественной ошибке определения для char-версии 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
Я сбросил свой объектный файл .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 ====
Это то, что я получил на своем Ubuntu с g++-9 после удаления inline
из 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()' помечен T
, что не позволяет иметь несколько определений; но «in-версия 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)
Ваш c++filt
меняет вывод? В Linux nm
показывает W
(для символов слабый) вместо T
(для символов текст) для этих экземпляров. Кроме того, ваш код компилируется в Linux с помощью GCC 7.5. Так что, возможно, предоставление вашей версии компилятора также может помочь (и важно) воспроизвести проблему.
@Дж.П.С. мой g++ - 11.2, а c++filt не меняет второй столбец, но я нахожусь в среде Cygwin. Если в Linux есть W
, то я могу понять, почему на этапе связывания нет нарушения ODR. Мой код, который я представил, может компилироваться, но если я удалю ключевое слово «inline» из tmplhdr.hpp, компиляция завершится ошибкой. извините за непоследовательность в моем вопросе.
А, думаю, я вижу проблему. Попробуйте сначала скомпилировать оба файла по отдельности с помощью g++ -c main.cpp
и g++ -c other.cpp
. Затем проверьте символы с nm
в обоих объектных файлах (main.o
и other.o
). Вы можете заметить разницу с ключевым словом inline
и без него. В моем случае оба символа max
были слабыми в обоих объектных файлах, если я не удалил ключевое слово inline
, и в этом случае одним из четырех символов был T
, что могло вызвать конфликт во время компоновки (между символом T
и W
символом max(char,char)
обоих объектные файлы).
@Дж.П.С. Да, это ответ, который я ищу. Мы, наконец, получили это. Спасибо.
@TonySu Если вам интересно, куда делись мои комментарии: похоже, мои комментарии больше не служат цели (вопрос был улучшен), поэтому я удалил их. Не стесняйтесь удалять свои собственные комментарии (особенно ответы мне), если вы считаете, что они изжили свою полезность, и вы хотите сохранить этот раздел комментариев в чистоте. ;) (Не удаляйте их, если вы видите ценность в их сохранении.)
@JaMiT Я все еще очень ценю ваши подсказки, которые, наконец, помогли мне прояснить мои вопросы и предоставить минимальный экспериментальный пример кода.
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?
Вы должны понимать, что вывод nm
нет дает вам полную картину.
nm
является частью binutils и использует libbfd
. Это работает следующим образом: различные форматы объектных файлов анализируются в libbfd
-внутреннее представление, а затем такие инструменты, как nm
, печатают это внутреннее представление в удобочитаемом формате.
Некоторые вещи "теряются при переводе". Вот почему вы должны ~ никогда не использовать, например. objdump
просматривать ELF
файлы (по крайней мере, не таблицу символов ELF
файлов).
Как вы правильно поняли, причина, по которой в Linux разрешено использование нескольких символов max<int>()
, заключается в том, что компилятор выдает их как символ W
(слабо определенный).
То же самое верно и для Windows, кроме Windows использует более старый формат COFF
, в котором нет слабых символов. Вместо этого символ помещается в специальный раздел .linkonce.$name
, и компоновщик знает, что он может выбрать любой такой раздел в ссылку, но должен делать только это однажды (т. е. он знает, что нужно отбросить все другие дубликаты этого раздела в любом другом объектном файле). ).
Ага. вы разгадали загадку среды Cygwin. Теперь я думаю, что загадка решена как в среде Linux, так и в среде Cygwin. Очень ценю ваш ответ!!!
Полная специализация больше не является шаблоном (нечего выводить) и ведет себя как «обычная» функция (которая также требует
inline
в заголовке).