27

友人が「ベース」型の非基本クラス オブジェクトをクラス型オブジェクト「派生」にキャストする場合があります。「派生」は「ベース」の派生クラスであり、関数のみを追加しますが、データは追加しません。以下のコードではx派生クラスにデータ メンバーを追加しました。

struct A {
  int a;
};

struct B : A {
  // int x;
  int x;
};

A a;

int g(B *b) {
   a.a = 10;
   b->a++;
   return a.a;
}

厳密なエイリアス分析をオンにすると、GCC (Clang も同様) は常に を返します。明確に定義されたコードでは を指すことができない10ため11です。ただし、削除すると(友人のコードで実際にそうであるように)、GCC の出力アセンブラー コードはのリターン アクセスを最適化せ、メモリから値をリロードします。したがって、GCCで「動作」を呼び出す友人のコードは(彼が意図したように)、まだ未定義の動作をしていると思いますがbaB::xa.ag

g((B*)&a);

したがって、本質的に同じ 2 つのケースで、GCC は一方のケースを最適化し、もう一方のケースを最適化しません。合法的に指すことbができるからですか?それとも、GCC が実際のコードを壊したくないだけなのでしょうか?a


私は述べている答えをテストしました

B::x を削除すると、B は標準レイアウト クラスの 9p7 の要件を満たし、アクセスは完全に明確になります。これは、2 つの型がレイアウト互換である 9.2p17 であるためです。

2 つのレイアウト互換列挙型を使用

enum A : int { X, Y };
enum B : int { Z };

A a;

int g(B *b) {
   a = Y;
   *b = Z;
   return a;
}

とがレイアウト互換 (7.2p8)であるにもかかわらず、のアセンブラ出力はではなくをg返します。10AB


したがって、私のさらなる質問は(回答を引用して)次のとおりです。「まったく同じレイアウトを持つ2つのクラスは「ほぼ同じ」と見なされ、最適化から除外されます。」. 誰かがGCCまたはClangについてこれを証明できますか?

4

4 に答える 4

8

を削除するB::xと、標準レイアウト クラスBの 9p7 の要件が満たされ、2 つの型がlayout-compatible 9.2p17 であり、両方のメンバーが同じ型であるため、アクセスが完全に明確に定義されます。


標準レイアウト クラスは、次のようなクラスです。

  • タイプ非標準レイアウトクラス(またはそのようなタイプの配列)または参照の非静的データメンバーを持たない、
  • 仮想関数 (10.3) も仮想基本クラス (10.1) もありません。
  • すべての非静的データメンバーに対して同じアクセス制御 (第 11 節) を持ち、
  • 非標準レイアウトの基本クラスはありません。
  • 最も派生したクラスに非静的データ メンバーがなく、非静的データ メンバーを持つ基本クラスが最大で 1 つあるか、または非静的データ メンバーを持つ基本クラスがない。
  • 最初の非静的データ メンバーと同じ型の基底クラスはありません。

2 つの標準レイアウト構造体型は、同じ数の非静的データ メンバーを持ち、対応する非静的データ メンバー (宣言順) がレイアウト互換型を持つ場合、レイアウト互換性があります。

于 2013-06-19T14:57:02.947 に答える
0

以下は正当な C++ であると思います (UB を呼び出さずに):

#include <new>

struct A {
  int a;
};

struct B : A {
  // int x;
};

static A a;

int g(B *b);
int g(B *b) {
   a.a = 10;
   b->a++;
   return a.a;
}

int f();
int f() {
  auto p = new (&a) B{};
  return g(p);
}

(グローバル)aは常に type のオブジェクトを参照し(の呼び出し後の -objectAのサブオブジェクトであっても) 、 type のオブジェクトを指すためです。Bf()pB

a(上記で行ったように) ストレージ期間を持つようにマークするstaticと、テストしたすべてのコンパイラは厳密なエイリアシングを喜んで適用し、最適化して を返し10ます。

一方、へのポインターを返す関数でマークまたは追加するg()と、__attribute__((noinline))h()a

A* h();
A* h() { return &a; }

私がテストしたコンパイラは&a、パラメータbがエイリアスを作成して値をリロードできると想定しています。

于 2017-05-03T16:21:59.113 に答える