Emissão dupla de símbolos construtores

Hoje, descobri uma coisa bastante interessante sobre g++ ou nm … as definições de construtor parecem ter duas inputs nas bibliotecas.

Eu tenho uma coisa de header.hpp:

 class Thing { Thing(); Thing(int x); void foo(); }; 

E thing.cpp :

 #include "thing.hpp" Thing::Thing() { } Thing::Thing(int x) { } void Thing::foo() { } 

Eu compilo isso com:

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

Então, eu corro o nm nele:

 %> 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 você pode ver, ambos os construtores de Thing são listados com duas inputs na biblioteca estática gerada. Meu g++ é o 4.4.3, mas o mesmo comportamento acontece no clang , então não é apenas um problema do gcc .

Isso não causa nenhum problema aparente, mas fiquei me perguntando:

  • Por que os construtores definidos são listados duas vezes?
  • Por que isso não causa problemas de “definição múltipla de símbolo __”?

EDIT : Para Carl, a saída sem o 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 você pode ver … a mesma function está gerando vários símbolos, o que ainda é bastante curioso.

E enquanto estamos nisso, aqui está uma seção do assembly gerado:

 .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 

Então o código gerado é … bem … o mesmo.


EDIT : Para ver qual construtor realmente é chamado, eu mudei Thing::foo() para isso:

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

O assembly gerado é:

 .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 isso, está invocando o construtor de object completo.

Vamos começar declarando que o GCC segue o Itanium C ++ ABI .


De acordo com a ABI, o nome mutilado para o seu Thing::foo() é facilmente analisado:

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

Você pode ler os nomes dos construtores da mesma forma, conforme abaixo. Observe como o construtor “name” não é fornecido, mas em vez disso, uma cláusula C :

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

Mas o que é isso C1 ? Sua duplicata tem C2 . O que isso significa ?

Bem, isso é bem simples também :

   ::= C1 # complete object constructor ::= C2 # base object constructor ::= C3 # complete object allocating constructor ::= D0 # deleting destructor ::= D1 # complete object destructor ::= D2 # base object destructor 

Espere, por que isso é simples ? Esta class não tem base. Por que ele tem um “construtor de object completo” e um “construtor de object base” para cada um?

  • Esse Q & A implica para mim que isso é simplesmente um subproduto do suporte ao polymorphism, mesmo que não seja realmente necessário neste caso.

  • Note que o c++filt costumava include esta informação em sua saída, mas não faz mais nada .

  • Este post do fórum faz a mesma pergunta, e a única resposta não melhora em respondê-la, exceto pela implicação de que o GCC poderia evitar a emissão de dois construtores quando o polymorphism não está envolvido, e que esse comportamento deveria ser melhorado no futuro. .

  • Esta publicação de newsgroup descreve um problema com a configuração de pontos de interrupção em construtores devido a essa emissão dual. É afirmado novamente que a raiz do problema é o suporte ao polymorphism.

Na verdade, isso está listado como um “problema conhecido” do GCC :

O G ++ emite duas cópias de construtores e destruidores.

Em geral, existem três tipos de construtores (e destruidores).

  • O object completo construtor / destrutor.
  • O construtor / destrutor do object base.
  • O construtor de alocação / destruidor de desalocação.

Os dois primeiros são diferentes, quando as classs base virtuais estão envolvidas.


O significado desses diferentes construtores parece ser o seguinte :

  • O “construtor de object completo”. Além disso, constrói classs base virtuais.

  • O “construtor do object base”. Cria o próprio object, assim como membros de dados e classs base não virtuais.

  • O “construtor de object de alocação”. Ele faz tudo que o construtor de objects completo faz, além de chamar o operador new para realmente alocar a memory … mas aparentemente isso não é normalmente visto.

Se você não tem nenhuma class base virtual, [os dois primeiros] são idênticos; O GCC irá, em níveis de otimização suficientes, realmente aliasar os símbolos ao mesmo código para ambos.