4

2つのソースファイルの同じ名前のクラスに含まれるクラス定義を決定するものは何ですか?を参照してください。、 1つの定義規則の意図的で明確な違反がありますが、コンパイラー/リンカーが1つの定義を別の定義から選択するオプションを持つことがどのように可能であるかについてはまだ混乱しています。

(回答/コメントに基づく補遺:意図的に標準に違反しているため、コードが未定義の動作をもたらす場合、コンパイラー/リンカーが以下に示す結果を生成する方法の1つの例を探しています。)

コードサンプルは次のとおりです。

// file1.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(1) {}
    int a;
};

int main()
{
    // foo() <-- uncomment this line to draw in file2.cpp's use of class A

    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

..。

//file2.h:

void foo();

..。

// file2.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(2) {}
    int a;
};

void foo()
{
    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

この場合、関数foo()は1を出力することもあれば、2を出力することもあります。

しかし、のコンストラクターAはインラインです!関数呼び出しではありません!したがって、コンパイラには、関数のコンパイル時aに関数自体のコンパイル済みコード内にオブジェクトをインスタンス化するコードのアセンブリ/マシン命令を含める必要があると思います。foo()foo()

したがって、後で、リンク時に、リンカーは、コンパイルされたバイナリfoo()に関数を含めることを決定したときの定義について、アセンブリ/マシン命令を変更しないと思います(実際には呼び出されていることがわかっているだけなので) 、リンク時)。この理由によると、リンカーはどのインラインコンストラクターコードが関数にコンパイルされるかに影響を与える可能性がないため、One Definition Ruleの意図的な違反にもかかわらず、常に使用されるインラインコンストラクターのfile2バージョンである必要があります。foo()foo()foo()

のコンストラクターAがインラインでない場合、関数foo()がコンパイルされるときに、関数へのJUMPステートメント(のコンストラクターA)が関数のアセンブルされたコード内に配置される可能性があることを理解しますfoo()。その後、リンカ時に、リンカはJUMPステートメントのアドレスに、のコンストラクタの2つの定義を選択して入力できAます。

実際には、インラインコンストラクターが存在するにもかかわらず、印刷する場合とfoo()印刷する場合があるという事実について私が考えることができる唯一の説明は、コンパイラーが「file2.cpp」をコンパイルするときに、コンパイルされたアセンブリ/マシンコードでスペースを作成することです。 Aのコンストラクターへのインライン呼び出しの関数ですが、実際にはアセンブリ/マシンコード自体を入力しません。その後、リンケージ時に、リンカーは、コンストラクターのインライン関数の2つの定義の間の(任意の)選択を使用して、関数自体のコンパイル済み定義内の事前に決定された場所にコンストラクターのコードをコピーします。 。1foo()2foo()Afoo()A

私の説明は正しいですか、それとも別の説明がありますか?Aこの例では、1つの定義規則に故意に違反しているにもかかわらず、コンストラクターの呼び出しがインラインである場合に、コンパイラー/リンカーがどのコンストラクターを呼び出すかを選択できるようにするにはどうすればよいでしょうか。

補遺:コメントと回答に応じて、タイトルを変更し、上部に説明の段落を追加して、この例では動作が定義されていないことを理解し、方法の1つの例を探していることを明確にしました実際のコンパイラ/リンカは、観察された動作を一度でも生成する可能性があります。特定の時点での動作を予測する答えを探しているわけではないことに注意してください。

補遺2:コメントに応えてA a;、VSデバッガーの行にブレークポイントを設定し、「逆アセンブリ」ビューを選択しました。実際、逆アセンブルコードから、「インライン」の存在にもかかわらず、この場合、コンパイラーはオブジェクトのコンストラクター呼び出しをインライン化しないことを選択したことは明らかですa

線の分解図`Aa;  質問のコードサンプルから:コンパイラーは、コンストラクター呼び出しがインラインであるにもかかわらず、コンストラクター呼び出しをインライン化しないことを選択しました。

したがって、Alfの答えは正しいです。コンストラクターの暗黙inlineにもかかわらず、コンストラクター呼び出しはインライン化されていません。

したがって、接線の問題が発生します。コンストラクターが通常のメンバー関数よりもインライン化される可能性が低いかどうかについて、明確なステートメントを作成できますか(inlineどちらの場合も、明示的または暗黙的に存在すると仮定します)。これについてステートメントを作成でき、答えが「はい、コンパイラーは通常のメンバー関数inlineを拒否するよりもコンストラクターを拒否する可能性が高いinline」である場合、フォローアップの質問は「なぜ」でしょうか。

4

3 に答える 3

11

クラス定義でコンストラクターを定義することは、キーワードinlineとそのクラス外の定義を使用することと同じです。

inlineマシンコードのインライン展開を必要としない/保証しません。それはそれについてほのめかします、しかしそれはすべてです。

の保証された効果はinline、複数の変換単位で関数の同じ定義を許可することです(次に、それが使用されるすべての変換単位で、本質的に同じように定義する必要があります)。

したがって、呼び出しの必須/保証されたインライン展開の仮定に基づくロジックは、誤った仮定のために誤った結論をもたらします。

于 2012-11-29T12:27:45.100 に答える
1

コードがリンクする可能性があることに本当に驚いています。リンカが名前の衝突について不平を言っているので、MSVisualStudioとはリンクしていないとほぼ確信しています。

このような場合、構造体に別の名前を付けたくない場合は、構造体の定義に匿名の名前空間を使用できます。

于 2012-11-29T12:29:16.417 に答える
0

ODRの違反は、診断を必要としません。つまり、これを行うと、プログラムの動作は未定義になります。何かが発生する可能性があります。この例では、すべてのメンバー関数がインラインであるため、リンカーはおそらく関与していません。そのため、コードが文句なしにコンパイルおよびリンクされ、本来の動作を実行する可能性があります。それにもかかわらず、規格はそれが起こることを約束していません。それが未定義動作の意味です。

于 2012-11-29T12:33:50.860 に答える