我的程序中有一个示例类,如下所示
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
有人可以解释这些OBJECT对应的内容吗?是MyTemplate1类的std :: type_info的这些全局实例吗?引擎盖下究竟发生了什么?
您不需要在编译单元中构造任何实例化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
对象符号变弱而不仅仅是全局?为什么它在目标文件的不同部分定义它们? (我的目标文件的第10和15节,你的第2894和2899节)。
如果我们检查这些部分还有什么:
$ 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
每个都是一个只读数据部分,意思是:
__attribute__((section(.rodata._ZTI11MyTemplate1IiE)))
__attribute__((section(.rodata._ZTI11MyTemplate1IfE)))
它只包含对象的定义,之后它被命名。
编译器为每个对象提供一个数据部分给所有对象,原因与它使符号WEAK
相同。 typeid(MyTemplate1<X>)
对于任意类型X
的引用可能是在#include
定义MyTemplate1
的同一链接中的多个翻译单元中进行的。为了防止在具有多个定义错误的情况下的链接失败,编译器使符号变弱。链接器将容忍弱符号的多个定义,将所有引用简单地解析为呈现自身的第一个定义并忽略其余的。通过将唯一的数据部分(或适当的函数部分)专用于每个弱模板实例化符号的定义,编译器可以自由地丢弃任何剩余的数据或函数部分,这些部分定义相同的弱符号而没有风险对该计划的附带损害。看到:
$ 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
中的定义。 mapfile显示:
mapfile(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
的定义是相互关联的:
mapfile(2)
...
Discarded input sections
...
.rodata._ZTI11MyTemplate1IiE
0x0000000000000000 0x10 bar.o
...
.rodata._ZTI11MyTemplate1IfE
0x0000000000000000 0x10 bar.o
...
bar.o
的人被扔掉了。这个先到先得的链接器原理很好,因为 - 并且只是因为 - 编译器在翻译单元template<class T> MyTemplate1
中找到的foo.cpp
的定义与它在bar.cpp
中找到的定义相同,这是C ++标准要求的条件在the One Definition Rule中,但C ++编译器无法执行任何操作。
您可以对模板实例化符号进行基本相同的观察,并且您使用clang ++看到的内容与您在g ++中看到的基本相同。