汇编TypeID(OBJ)时会发生什么-C

what happens when typeid(obj) is compiled - C++

本文关键字:什么 TypeID OBJ 汇编      更新时间:2023-10-16

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

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>的类型Info和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

有人可以解释这些对象与什么相对应吗?这些std :: type_info的全局实例是MyTemplate1的这些全局实例吗?引擎盖下到底发生了什么?

您不需要构造任何对象实例化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是指静态存储持续时间的对象

引擎盖下到底发生了什么?

您可能会想知道:为什么编译器将这些typeinfo对象符号而不是简单的全局?为什么它在对象文件的不同部分中定义它们?(我的对象文件的第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

每个都是仅读取的数据表,其意义是:

__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

foo.o之前输入bar.o,然后链接器选择的定义 _ZTI11MyTemplate1I(f|i)E的CC_17,无视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.cppbar.cpp中发现的单元相同条件C 标准需要,在一个定义规则中,但是C 编译器对 emforce

通常,您可以对模板构成符号进行基本相同的观察结果,而您对Clang 的看到的观察与G 所看到的基本相同。