Double émission de symboles constructeurs

Aujourd’hui, j’ai découvert une chose plutôt intéressante à propos de g++ ou nm … les définitions de constructeur semblent avoir deux entrées dans les bibliothèques.

J’ai un en-tête thing.hpp :

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

Et thing.cpp :

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

Je comstack ceci avec:

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

Ensuite, je lance nm sur celui-ci:

 %> 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 

Comme vous pouvez le constater, les deux constructeurs de Thing sont répertoriés avec deux entrées dans la bibliothèque statique générée. Mon g++ est 4.4.3, mais le même comportement se produit dans clang , donc ce n’est pas juste un problème de gcc .

Cela ne pose aucun problème apparent, mais je me demandais:

  • Pourquoi les constructeurs définis sont-ils listés deux fois?
  • Pourquoi cela ne cause-t-il pas “une définition multiple des problèmes de symbole __”?

EDIT : Pour Carl, la sortie sans l’argument C :

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

Comme vous pouvez le voir, la même fonction génère plusieurs symboles, ce qui rest assez curieux.

Et pendant que nous y sums, voici une section de l’assemblage généré:

 .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 

Le code généré est donc … eh bien … le même.


EDIT : Pour voir quel constructeur est réellement appelé, j’ai changé Thing::foo() pour ceci:

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

L’assemblage généré est:

 .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 

Donc, il appelle le constructeur d’object complet.

Nous commencerons par déclarer que GCC suit l’IAB C ++ d’Itanium .


Selon l’ABI, le nom mutilé de votre Thing::foo() est facilement analysé:

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

Vous pouvez lire les noms de constructeur de la même manière, comme ci-dessous. Remarquez comment le constructeur “name” n’est pas donné, mais plutôt une clause C :

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

Mais quel est ce C1 ? Votre copie a C2 . Qu’est-ce que cela signifie ?

Eh bien, c’est assez simple aussi :

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

Attendez, pourquoi est-ce simple ? Cette classe n’a pas de base. Pourquoi a-t-il un “constructeur d’object complet” et un “constructeur d’object de base” pour chacun?

  • Ce Q & A m’implique qu’il s’agit simplement d’un sous-produit du support du polymorphism, même s’il n’est pas réellement requirejs dans ce cas.

  • Notez que c++filt incluait cette information dans sa sortie démanglée, mais ne l’a plus .

  • Ce post sur le forum pose la même question, et la seule réponse ne fait pas mieux pour y répondre, à l’exception de l’implication que GCC pourrait éviter d’émettre deux constructeurs lorsque le polymorphism n’est pas impliqué, et que ce comportement devrait être amélioré à l’avenir .

  • Cet article décrit un problème lié à la définition de points d’arrêt dans les constructeurs en raison de cette double émission. Il est à nouveau indiqué que la racine du problème est la prise en charge du polymorphism.

En fait, ceci est répertorié comme un “problème connu” du GCC :

G ++ émet deux copies de constructeurs et de destructeurs.

En général, il existe trois types de constructeurs (et de destructeurs).

  • Le constructeur / destructeur d’object complet.
  • Le constructeur / destructeur d’object de base.
  • Le constructeur / destructeur allouant.

Les deux premiers sont différents lorsque des classes de base virtuelles sont impliquées.


La signification de ces différents constructeurs semble être la suivante :

  • Le “constructeur d’object complet”. Il construit en outre des classes de base virtuelles.

  • Le “constructeur d’object de base”. Il crée l’object lui-même, ainsi que des membres de données et des classes de base non virtuelles.

  • Le “constructeur d’object atsortingbutaire”. Il fait tout ce que fait le constructeur d’object complet, en plus d’appeler l’opérateur new pour allouer réellement la mémoire … mais apparemment, cela n’est généralement pas vu.

Si vous n’avez pas de classes de base virtuelles, [les deux premières] sont identiques; GCC, à des niveaux d’optimisation suffisants, alias effectivement les symboles avec le même code pour les deux.