1. ホーム
  2. c++

[解決済み] コンストラクタ記号のデュアルエミッション

2022-09-29 05:52:12

質問

今日、私はどちらかについて、ちょっと面白いことを発見しました。 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

つまり、完全なオブジェクトのコンストラクタを呼び出しているわけです。

どのように解決するのですか?

まず最初に宣言するのは GCCは以下のように Itanium C++ ABI .


ABIによると、あなたの Thing::foo() は簡単にパースされます。

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

以下のように、コンストラクタの名前も同様に読むことができます。コンストラクタの "name"が与えられていないことに注意してください。 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


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

  • この Q&A は、この場合実際には必要でないにもかかわらず、これが単にポリモーフィズムサポートの副産物であることを私に暗示します。

  • 以下のことに注意してください。 c++filt はこの情報をデマングル出力に含んでいたことに注意してください。 に含まれていましたが、今は含まれていません。 .

  • このフォーラムの投稿 は同じ質問を投げかけていますが、唯一の回答は、GCCの 多相性が関与していないとき、GCC は 2 つのコンストラクタを生成しないようにすることができ、この動作は将来的に改善されるべきであるということを暗に示しているだけです。

  • このニュースグループへの投稿 には、このデュアル エミッションによるコンストラクタでのブレークポイントの設定に関する問題が記述されています。問題の根源はポリモーフィズムのサポートであることが改めて述べられています。

実際には は GCC の "known issue" としてリストアップされています。 :

<ブロッククオート

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

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

  • 完全なオブジェクトのコンストラクタ/デストラクタです。
  • ベース・オブジェクトのコンストラクタ/デストラクタ。
  • アロケートするコンストラクタ/デストラクタです。

最初の2つは、仮想基底クラスが含まれる場合、異なります。 が関係してきます。


これらの異なるコンストラクタの意味 は次のようなものであると思われます。 :

  • 完全なオブジェクトのコンストラクタです。これはさらに仮想基底クラスを構築します。

  • 基本オブジェクトのコンストラクタです。これは、オブジェクト自身、データ・メンバ、および非仮想ベース・クラスを作成します。

  • オブジェクトの割り当てを行うコンストラクタです。これは完全なオブジェクト コンストラクタが行うすべてのことを行い、さらに実際にメモリを割り当てるために演算子 new を呼び出します... が、どうやらこれは通常見られないようです。

<ブロッククオート

仮想基底クラスがない場合、[最初の2つは]同一です。 GCCは、十分な最適化レベルにおいて、実際に両者に対して同じコードへのエイリアスを作成します。 を作成します。