c++ - Emisión dual de símbolos de constructor
gcc (1)
Hoy, descubrí algo bastante interesante acerca de g++
o nm
... las definiciones de los constructores parecen tener dos entradas en las bibliotecas.
Tengo un header thing.hpp
:
class Thing
{
Thing();
Thing(int x);
void foo();
};
Y thing.cpp
:
#include "thing.hpp"
Thing::Thing()
{ }
Thing::Thing(int x)
{ }
void Thing::foo()
{ }
Compilo esto con:
g++ thing.cpp -c -o libthing.a
Entonces, corro nm
en él:
%> 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
Como puede ver, ambos constructores para Thing
se enumeran con dos entradas en la biblioteca estática generada. Mi g++
es 4.4.3, pero el mismo comportamiento ocurre en clang
, por lo que no es solo un problema de gcc
.
Esto no causa ningún problema aparente, pero me preguntaba:
- ¿Por qué los constructores definidos se enumeran dos veces?
- ¿Por qué esto no causa problemas de "definición múltiple de símbolo __"?
EDITAR : Para Carl, la salida sin el argumento C
:
%> nm -g libthing.a
0000000000000030 T _ZN5Thing3fooEv
0000000000000022 T _ZN5ThingC1Ei
000000000000000a T _ZN5ThingC1Ev
0000000000000014 T _ZN5ThingC2Ei
0000000000000000 T _ZN5ThingC2Ev
U __gxx_personality_v0
Como puede ver ... la misma función genera múltiples símbolos, lo cual es bastante curioso.
Y mientras estamos en esto, aquí hay una sección de ensamblaje generado:
.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
Entonces el código generado es ... bueno ... lo mismo.
EDITAR : Para ver qué constructor se llama realmente, cambié Thing::foo()
a esto:
void Thing::foo()
{
Thing t;
}
El ensamblaje generado es:
.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
Por lo tanto, invoca al constructor de objetos completo.
Comenzaremos declarando que GCC sigue el Itanium C ++ ABI .
Según ABI, el nombre destrozado para su Thing::foo()
se analiza fácilmente:
_Z | N | 5Thing | 3foo | E | v
prefix | nested | `Thing` | `foo`| end nested | parameters: `void`
Puede leer los nombres de los constructores de manera similar, como se muestra a continuación. Observe cómo no se da el "nombre" del constructor, sino una cláusula C
:
_Z | N | 5Thing | C1 | E | i
prefix | nested | `Thing` | Constructor | end nested | parameters: `int`
Pero, ¿qué es esto C1
? Tu duplicado tiene C2
. ¿Qué significa esto?
Bueno, esto es bastante simple también :
<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
Espera, ¿por qué es esto simple ? Esta clase no tiene base. ¿Por qué tiene un "constructor de objetos completo" y un "constructor de objetos base" para cada uno?
Esta Q & A implica para mí que esto es simplemente un subproducto del soporte del polimorfismo, aunque en realidad no se requiere en este caso.
Tenga en cuenta que
c++filt
solía incluir esta información en su resultado exigido, pero ya no lo hace .Esta publicación en el foro hace la misma pregunta, y la única respuesta no mejora al contestarla, excepto por la implicación de que GCC podría evitar emitir dos constructores cuando el polimorfismo no está involucrado, y que este comportamiento debería mejorarse en el futuro. .
Esta publicación de grupos de noticias describe un problema con la configuración de puntos de interrupción en los constructores debido a esta doble emisión. Nuevamente se afirma que la raíz del problema es el soporte para el polimorfismo.
De hecho, esto aparece como un "problema conocido" de GCC :
G ++ emite dos copias de constructores y destructores.
En general, hay tres tipos de constructores (y destructores).
- El constructor / destructor de objetos completo.
- El constructor / destructor de objetos base.
- El constructor de asignación / desasignador de desasignación.
Los primeros dos son diferentes, cuando las clases base virtuales están involucradas.
El significado de estos diferentes constructores parece ser el siguiente :
El "constructor de objetos completo". También construye clases base virtuales.
El "constructor de objetos base". Crea el objeto en sí, así como los miembros de datos y las clases base no virtuales.
El "constructor de objeto de asignación". Hace todo lo que hace el constructor de objetos completo, además de que llama al operador nuevo para asignar realmente la memoria ... pero aparentemente esto no suele verse.
Si no tiene clases base virtuales, [las dos primeras] son idénticas; GCC, en niveles de optimización suficientes, alias los símbolos del mismo código para ambos.