我的程序中有一个示例类,如下所示
template<class T>
class MyTemplate1
{
public:
T a;
MyTemplate1(T other){
a = other;
}
};
在我的主程序中,如果我只是创建类型MyTemplate1<int>
的对象,则在readelf输出中不显示任何typeinfo对象。但是,如果我在
MyTemplate1<int> obj = 12;
if(typeid(obj) == typeid(MyTemplate1<float>))
//some code
readelf输出显示MyTemplate1<int>
的typeinfo和MyTemplate1<float>
的typeinfo。
$readelf -s -W <objfile> | findstr -I "MyTemplate"
9023: 00000000 8 OBJECT WEAK DEFAULT 2899 _ZTI11MyTemplate1IfE
9024: 00000000 8 OBJECT WEAK DEFAULT 2894 _ZTI11MyTemplate1IiE
有人可以解释一下这些对象对应什么吗? MyTemplate1类的这些std :: type_info的全局实例吗?到底发生了什么?
答案 0 :(得分:1)
您不需要构造任何实例化MyTemplate1<T>
的对象
在编译单元中查看描述实例化类的typeinfo对象
该模板在目标文件的全局符号表中的位置。你只需要
引用此类的typeid
:-
$ cat main.cpp
#include <typeinfo>
template<class T>
class MyTemplate1
{
public:
T a;
MyTemplate1(T other){
a = other;
}
};
int main(void)
{
return (typeid(MyTemplate1<int>) == typeid(MyTemplate1<float>));
}
$ clang++ -Wall -c main.cpp
$ readelf -s -W main.o | grep MyTemplate1
5: 0000000000000000 16 OBJECT WEAK DEFAULT 15 _ZTI11MyTemplate1IfE
6: 0000000000000000 16 OBJECT WEAK DEFAULT 10 _ZTI11MyTemplate1IiE
7: 0000000000000000 17 OBJECT WEAK DEFAULT 13 _ZTS11MyTemplate1IfE
8: 0000000000000000 17 OBJECT WEAK DEFAULT 8 _ZTS11MyTemplate1IiE
$ c++filt _ZTI11MyTemplate1IfE
typeinfo for MyTemplate1<float>
$ c++filt _ZTI11MyTemplate1IiE
typeinfo for MyTemplate1<int>
$ c++filt _ZTS11MyTemplate1IfE
typeinfo name for MyTemplate1<float>
$ c++filt _ZTS11MyTemplate1IiE
typeinfo name for MyTemplate1<int>
存在这些typeinfo
对象是因为,正如@Peter所说,C ++标准
要求typeid
refers to an object of static storage duration
引擎盖下到底发生了什么?
您可能会想:为什么编译器使这些typeinfo
对象符号 weak 而不是简单地变为全局符号?
为什么要在目标文件的不同部分中定义它们? (我的目标文件的第10和15节,
您的第2894和2899部分)。
如果我们检查以下部分中的 else :
$ readelf -s main.o | egrep '(10 |15 )'
5: 0000000000000000 16 OBJECT WEAK DEFAULT 15 _ZTI11MyTemplate1IfE
6: 0000000000000000 16 OBJECT WEAK DEFAULT 10 _ZTI11MyTemplate1IiE
我们看到每个对象在其部分中都是唯一的东西。为什么这样?
在我的main.o
中,第10和15部分是:
$ readelf -t main.o | egrep '(\[10\]|\[15\])'
[10] .rodata._ZTI11MyTemplate1IiE
[15] .rodata._ZTI11MyTemplate1IfE
其中每个都是只读的 data-section ,含义是:
__attribute__((section(.rodata._ZTI11MyTemplate1IiE)))
__attribute__((section(.rodata._ZTI11MyTemplate1IfE)))
不包含但包含的对象的定义 命名。
编译器为每个对象提供一个数据段,以供其自身使用
产生符号WEAK
的原因相同。
对于任意类型typeid(MyTemplate1<X>)
的引用X
可以在
与#include
的定义相同的链接中的多个翻译单元
MyTemplate1
。为了避免在这种情况下出现 multiple definition 错误的链接失败,
编译器使符号变弱。
链接器将容忍弱符号的多个定义,从而解决
所有引用都只针对第一个定义,该定义表示自己并忽略
其余的部分。通过将唯一的数据部分(或适当的功能部分)专用于
编译器为链接器提供了以下自由度:每个弱模板说明符号的定义
放弃任何定义相同弱符号的多余数据或功能部分
附带损害程序的风险。参见:
$ cat MyTemplate1.hpp
#pragma once
template<class T>
class MyTemplate1
{
public:
T a;
MyTemplate1(T other){
a = other;
}
};
$ cat foo.cpp
#include "MyTemplate1.hpp"
#include <typeinfo>
int foo()
{
return typeid(MyTemplate1<int>) == typeid(MyTemplate1<float>);
}
$ cat bar.cpp
#include "MyTemplate1.hpp"
#include <typeinfo>
int bar()
{
return typeid(MyTemplate1<int>) != typeid(MyTemplate1<float>);
}
$ cat prog.cpp
extern int foo();
extern int bar();
int main()
{
return foo() && bar();
}
如果我们进行编译:
$ clang++ -Wall -c prog.cpp foo.cpp bar.cpp
并像这样链接(带有一些诊断程序):
$ clang++ -o prog prog.o bar.o foo.o \
-Wl,-trace-symbol=_ZTI11MyTemplate1IfE \
-Wl,-trace-symbol=_ZTI11MyTemplate1IiE \
-Wl,-Map=mapfile
/usr/bin/ld: bar.o: definition of _ZTI11MyTemplate1IfE
/usr/bin/ld: bar.o: definition of _ZTI11MyTemplate1IiE
/usr/bin/ld: foo.o: reference to _ZTI11MyTemplate1IfE
/usr/bin/ld: foo.o: reference to _ZTI11MyTemplate1IiE
在bar.o
之前输入foo.o
,然后链接程序选择以下内容的定义
_ZTI11MyTemplate1I(f|i)E
中的bar.o
,而忽略了foo.o
中的定义,
将foo.o
中的引用解析为bar.o
中的定义。映射文件显示:
映射文件(1)
...
Discarded input sections
...
.rodata._ZTI11MyTemplate1IiE
0x0000000000000000 0x10 foo.o
...
.rodata._ZTI11MyTemplate1IfE
0x0000000000000000 0x10 foo.o
...
foo.o
中的定义被丢弃。如果我们重新链接
bar.o
和foo.o
的顺序颠倒了:
$ clang++ -o prog prog.o foo.o bar.o \
-Wl,-trace-symbol=_ZTI11MyTemplate1IfE \
-Wl,-trace-symbol=_ZTI11MyTemplate1IiE \
-Wl,-Map=mapfile
/usr/bin/ld: foo.o: definition of _ZTI11MyTemplate1IfE
/usr/bin/ld: foo.o: definition of _ZTI11MyTemplate1IiE
/usr/bin/ld: bar.o: reference to _ZTI11MyTemplate1IfE
/usr/bin/ld: bar.o: reference to _ZTI11MyTemplate1IiE
然后我们得到相反的结果。 foo.o
中的定义已链接
和:
映射文件(2)
...
Discarded input sections
...
.rodata._ZTI11MyTemplate1IiE
0x0000000000000000 0x10 bar.o
...
.rodata._ZTI11MyTemplate1IfE
0x0000000000000000 0x10 bar.o
...
bar.o
中的那些被丢弃。这先到先得
链接器的原理很好,因为-和 only 因为-编译器发现的template<class T> MyTemplate1
的定义
在翻译单元foo.cpp
中与在bar.cpp
中找到的那个 相同,
the One Definition Rule中C ++标准需要的条件,
但是C ++编译器无法执行强制。
关于模板实例化的符号,您基本上可以得到基本相同的观察结果,而使用clang ++可以看到的东西与使用g ++可以看到的东西基本相同。