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