100 votes

Double émission de symboles constructeurs

Aujourd'hui, j'ai découvert une chose assez intéressante à propos de l'un ou l'autre. g++ ou nm ...les définitions des constructeurs 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 compile cela avec :

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

Ensuite, j'exécute nm sur elle :

%> 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 voir, les deux constructeurs pour 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 en clang donc ce n'est pas seulement une gcc question.

Cela ne cause 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 des problèmes de "définition multiple du symbole __" ?

EDIT : Pour Carl, la sortie sans le C argument :

%> 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 est tout de même assez curieux.

Et pendant que nous y sommes, 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 modifié Thing::foo() à ça :

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

Il invoque donc le constructeur complet de l'objet.

140voto

Lightness Races in Orbit Points 122793

Nous allons commencer par déclarer que Le CCG suit l'ABI Itanium C++ .


Selon l'ABI, le nom tronqué 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 des constructeurs de manière similaire, comme ci-dessous. Remarquez comment le "nom" du constructeur n'est pas donné, mais plutôt un C clause :

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

Mais qu'est-ce que c'est ? C1 ? Votre duplicata a C2 . Que fait cette moyenne ?

Bien, c'est aussi très simple :

  <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

Attends, pourquoi est-ce que simple ? Cette classe n'a pas de base. Pourquoi a-t-elle un "constructeur d'objet complet" ? et un "constructeur d'objet de base" pour chacun ?

  • Ce Q&R me laisse entendre qu'il s'agit simplement d'un sous-produit de la prise en charge du polymorphisme, même s'il n'est pas réellement nécessaire dans ce cas.

  • Notez que c++filt utilisé pour inclure cette information dans sa sortie démêlée, mais ne le fait plus .

  • Ce message du forum pose la même question, et la seule réponse ne fait pas mieux pour y répondre, sauf pour impliquer que GCC pourrait éviter d'émettre deux constructeurs lorsque le polymorphisme n'est pas impliqué, et que ce comportement devrait être amélioré à l'avenir.

  • Ce message de newsgroup décrit un problème de mise en place 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 le support du polymorphisme.

En fait, Ceci est listé comme un "problème connu" de GCC. :

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

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

  • Le constructeur/destructeur complet de l'objet.
  • Le constructeur/destructeur de l'objet de base.
  • Le constructeur allocateur/désallocateur destructeur.

Les deux premières sont différentes, lorsque les classes de base virtuelles sont sont concernées.


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

  • Le "constructeur d'objet complet". Il construit en outre des classes de base virtuelles.

  • Le "constructeur de l'objet de base". Il crée l'objet lui-même, ainsi que les membres de données et les classes de base non virtuelles.

  • Le "constructeur d'objet alloué". Il fait tout ce que le constructeur d'objet complet fait, plus il appelle l'opérateur new pour allouer la mémoire... mais apparemment cela ne se voit pas habituellement.

Si vous n'avez pas de classes de base virtuelles, [les deux premières] sont identiques ; GCC, à des niveaux d'optimisation suffisants, aliasera réellement les symboles au même code pour les deux.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X