web-dev-qa-db-ja.com

コンストラクターシンボルの二重放出

今日、_g++_またはnm...コンストラクター定義のどちらかについて、ライブラリーに2つのエントリーがあるように見えるという興味深いことがわかりました。

ヘッダー_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の両方のコンストラクタが2つのエントリとともにリストされています。私の_g++_は4.4.3ですが、同じ動作がclangでも発生するため、単なるgccの問題ではありません。

これは明らかな問題を引き起こしませんが、私は疑問に思いました:

  • 定義されたコンストラクタが2度リストされるのはなぜですか?
  • なぜこれが「シンボル__の複数定義」の問題を引き起こさないのですか?

[〜#〜] edit [〜#〜]:Carlの場合、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
_

したがって、生成されたコードは...まあ...同じです。


[〜#〜] edit [〜#〜]:実際に呼び出されるコンストラクタを確認するために、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
_

したがって、それは完全なオブジェクトコンストラクタを呼び出しています。

102
Travis Gockel

GCCが続くItanium C++ ABI と宣言することから始めます。


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

待って、なぜこれはsimpleなのですか?このクラスにはベースがありません。なぜ「完全なオブジェクトコンストラクタ」それぞれに「ベースオブジェクトコンストラクタ」があるのですか?

  • このQ&A は、実際には必要ない場合でも、ポリモーフィズムサポートの副産物であることを意味します。

  • ご了承ください c++filtは、デマングルされた出力にこの情報を含めるために使用されていました ただし、これ以上はありません

  • このフォーラムの投稿 は同じ質問をしますが、GCCができたという意味を除いて、唯一の応答はそれに対する回答で何の効果もありませんポリモーフィズムが含まれていない場合に2つのコンストラクターを発行しないようにします。この動作は将来改善されるはずです。

  • このニュースグループの投稿 は、このデュアルエミッションのために、コンストラクターにブレークポイントを設定する際の問題について説明しています。問題の根本は多態性のサポートであると再び述べられています。

実際、 これはGCCの「既知の問題」としてリストされています

G ++は、コンストラクタとデストラクタの2つのコピーを発行します。

一般に、3種類のコンストラクタ(およびデストラクタ)があります。

  • 完全なオブジェクトコンストラクタ/デストラクタ。
  • 基本オブジェクトのコンストラクタ/デストラクタ。
  • 割り当てコンストラクタ/割り当て解除デストラクタ。

最初の2つは、仮想基本クラスが含まれる場合は異なります。


これらの異なるコンストラクタの意味 次のように見える

  • 「完全なオブジェクトコンストラクタ」。さらに、仮想基本クラスを構築します。

  • 「ベースオブジェクトコンストラクタ」。オブジェクト自体、およびデータメンバーと非仮想基本クラスを作成します。

  • 「割り当てオブジェクトコンストラクター」。これは、完全なオブジェクトコンストラクターが行うすべてを実行し、さらに、オペレーターnewを呼び出して実際にメモリを割り当てます...しかし、これは通常は表示されません

仮想基本クラスがない場合、[最初の2つ]は同じです。 GCCは、十分な最適化レベルで、実際には両方のシンボルを同じコードにエイリアスします。