Двойная эмиссия символов конструктора - PullRequest
90 голосов
/ 03 августа 2011

Сегодня я обнаружил довольно интересную вещь о g++ или nm ... в определениях конструктора, похоже, есть две записи в библиотеках.

У меня есть заголовок thing.hpp:

class Thing
{
    Thing();

    Thing(int x);

    void foo();
};

И thing.cpp:

#include "thing.hpp"

Thing::Thing()
{ }

Thing::Thing(int x)
{ }

void Thing::foo()
{ }

Я компилирую это с помощью:

g++ thing.cpp -c -o libthing.a

Затем я запускаю nm на нем:

%> nm -gC libthing.a
0000000000000030 T Thing::foo()
0000000000000022 T Thing::Thing(int)
000000000000000a T Thing::Thing()
0000000000000014 T Thing::Thing(int)
0000000000000000 T Thing::Thing()
                 U __gxx_personality_v0

Как видите, оба конструктора для Thing перечислены с двумя записями в сгенерированной статической библиотеке.Мой g++ - это 4.4.3, но то же самое происходит в clang, так что это не просто проблема gcc.

Это не вызывает никаких явных проблем, но мне было интересно:

  • Почему определенные конструкторы перечислены дважды?
  • Почему это не вызывает проблем с «множественным определением символа __»?

EDIT : Для Carl вывод без аргумента C:

%> nm -g libthing.a
0000000000000030 T _ZN5Thing3fooEv
0000000000000022 T _ZN5ThingC1Ei
000000000000000a T _ZN5ThingC1Ev
0000000000000014 T _ZN5ThingC2Ei
0000000000000000 T _ZN5ThingC2Ev
                 U __gxx_personality_v0

Как вы можете видеть ... та же функция генерирует несколько символов, что все еще довольно любопытно.

И пока мы здесь, вот часть сгенерированной сборки:

.globl _ZN5ThingC2Ev
        .type   _ZN5ThingC2Ev, @function
_ZN5ThingC2Ev:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movq    %rdi, -8(%rbp)
        leave
        ret
        .cfi_endproc
.LFE1:
        .size   _ZN5ThingC2Ev, .-_ZN5ThingC2Ev
        .align 2
.globl _ZN5ThingC1Ev
        .type   _ZN5ThingC1Ev, @function
_ZN5ThingC1Ev:
.LFB2:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movq    %rdi, -8(%rbp)
        leave
        ret
        .cfi_endproc

Итак, сгенерированный код ... ну ... такой же.


EDIT : чтобы увидеть, какой конструктор действительно вызывается, я изменил Thing::foo() на следующее:

void Thing::foo()
{
    Thing t;
}

Сгенерированная сборка:

.globl _ZN5Thing3fooEv
        .type   _ZN5Thing3fooEv, @function
_ZN5Thing3fooEv:
.LFB550:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $48, %rsp
        movq    %rdi, -40(%rbp)
        leaq    -32(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN5ThingC1Ev
        leaq    -32(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN5ThingD1Ev
        leave
        ret
        .cfi_endproc

Так что он вызывает полный конструктор объекта.

1 Ответ

125 голосов
/ 03 августа 2011

Начнем с объявления, что GCC следует Itanium C ++ ABI .


Согласно ABI, искаженное имя для вашего Thing::foo() легко разбирается:

_Z     | N      | 5Thing  | 3foo | E          | v
prefix | nested | `Thing` | `foo`| end nested | parameters: `void`

Вы можете прочитать имена конструкторов аналогично, как показано ниже. Обратите внимание, что имя конструктора не задано, а вместо него выражение C:

_Z     | N      | 5Thing  | C1          | E          | i
prefix | nested | `Thing` | Constructor | end nested | parameters: `int`

Но что это C1? Ваш дубликат имеет C2. Что это означает ?

Ну, это тоже довольно просто :

  <ctor-dtor-name> ::= C1   # complete object constructor
                   ::= C2   # base object constructor
                   ::= C3   # complete object allocating constructor
                   ::= D0   # deleting destructor
                   ::= D1   # complete object destructor
                   ::= D2   # base object destructor

Подождите, почему это просто ? Этот класс не имеет базы. Почему у него есть «полный конструктор объекта» и «базовый конструктор объекта» для каждого?

  • Эти вопросы и ответы означают для меня, что это просто побочный продукт поддержки полиморфизма, даже если в этом случае он фактически не требуется.

  • Обратите внимание, что c++filt используется для включения этой информации в его деформированный вывод, , но больше не .

  • В этом сообщении на форуме задается тот же вопрос, и единственный ответ не дает лучшего ответа на него, за исключением того, что GCC может избежать выброса двух конструкторы, когда полиморфизм не задействован, и что это поведение должно быть улучшено в будущем.

  • Эта публикация группы новостей описывает проблему с установкой точек останова в конструкторах из-за этого двойного излучения. Еще раз утверждается, что корень проблемы - поддержка полиморфизма.

Фактически, это указано как "известная проблема" GCC :

G ++ испускает две копии конструкторов и деструкторов.

Обычно существует три типа конструкторов (и деструкторов).

  • Полный конструктор / деструктор объекта.
  • Конструктор / деструктор базового объекта.
  • Распределяющий конструктор / освобождающий деструктор.

Первые два отличаются, когда виртуальные базовые классы участие.


Значение этих различных конструкторов выглядит следующим образом :

  • «Полный конструктор объекта». Он дополнительно создает виртуальные базовые классы.

  • «Конструктор базовых объектов». Он создает сам объект, а также члены данных и не виртуальные базовые классы.

  • «Конструктор распределительных объектов». Он делает все, что делает полный конструктор объекта, плюс вызывает оператор new для фактического выделения памяти ... , но, по-видимому, обычно этого не видно.

Если у вас нет виртуальных базовых классов, [первые два] тождественны; GCC, на достаточных уровнях оптимизации, на самом деле псевдоним символы к одному и тому же коду для обоих.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...