编译typeid(obj)时会发生什么-C ++

时间:2019-03-05 18:48:56

标签: c++ clang++ rtti libc++

我的程序中有一个示例类,如下所示

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的全局实例吗?到底发生了什么?

1 个答案:

答案 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.ofoo.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 ++可以看到的东西基本相同。