что происходит, когда typeid (obj) компилируется - C ++ - PullRequest
3 голосов
/ 05 марта 2019

У меня есть пример класса в моей программе, как показано ниже

template<class T>
class MyTemplate1
{
public:
    T a;

    MyTemplate1(T other){
        a = other;
    }
};

В моей основной программе, если я просто создаю объект типа MyTemplate1<int>, он не показывает никаких объектов типа info в выводе readelf.Но если я добавлю какой-нибудь код, как показано ниже

MyTemplate1<int> obj = 12;
if(typeid(obj) == typeid(MyTemplate1<float>))
   //some code

, вывод readelf показывает typeinfo для MyTemplate1<int> и typeinfo для MyTemplate1<float>.

$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?Что именно происходит под капотом?

1 Ответ

1 голос
/ 10 марта 2019

Вам не нужно создавать какие-либо объекты, инстанцирующие 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).

И если мы проверим, что еще находится в этих разделах:

$ 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 (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, a условие, что Стандарт C ++ требует , в Правило единого определения , но который компилятор C ++ ничего не может сделать для принудительного применения .

Вы можете сделать, по существу, те же самые наблюдения относительно символов создания шаблонов в целом, и то, что вы видите с помощью clang ++, по существу совпадает с тем, что вы увидите с g ++.

...