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

问题描述 投票:3回答:1

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

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的这些全局实例吗?引擎盖下究竟发生了什么?

c++ clang++ rtti libc++
1个回答
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对象符号变弱而不仅仅是全局?为什么它在目标文件的不同部分定义它们? (我的目标文件的第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.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的定义是相互关联的:

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 ++中看到的基本相同。

© www.soinside.com 2019 - 2024. All rights reserved.