Двойная эмиссия символов конструктора

Сегодня я обнаружил довольно интересную вещь о 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 вопрос.

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

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

РЕДАКТИРОВАТЬ: для Карла, выход без 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

Таким образом, сгенерированный код... ну... то же самое.


РЕДАКТИРОВАТЬ: Чтобы увидеть, что на самом деле вызывается конструктор, я изменил 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 ответ

Решение

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


Согласно 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, на достаточных уровнях оптимизации, на самом деле совмещает символы для одного и того же кода для обоих.

Другие вопросы по тегам