Cでの構造体拡張

Aug 21 2020

次のような2つの構造が定義されているとします。

typedef struct {
   T x;
   T y;
} A;

typedef struct {
   A a;
   T z;
} B;

構造体Bへのポインターを構造体Aへのポインターとして扱うことはできますか?

実際には、これは信頼できる/標準/ポータブル/コンパイラ不変です:

B b = {{1,2},3};
A * a = &b;

print(a->x);
print(a->y);

回答

3 Lundin Aug 21 2020 at 08:19

C17 6.7.2.1はこれを述べています(私の強調):

構造体オブジェクト内で、非ビットフィールドメンバーとビットフィールドが存在するユニットには、宣言された順序で増加するアドレスがあります。適切に変換された構造体オブジェクトへのポインタは、その最初のメンバー(または、そのメンバーがビットフィールドの場合は、それが存在するユニット)を指し、その逆も同様です。

これは、B bオブジェクトのポインタをその最初のメンバーの型に「適切に変換」する必要があることを意味します。この変換は暗黙的には発生しません。明示的なキャストを使用してこれを行う必要があります。

A * a = (A*)&b;

上記の引用部分にあるように、そうすることは明確で安全です。

同様に、コンパイラは、エイリアスAへのポインタとBエイリアスしないポインタを想定することはできません。有効なタイプ6.5.7(「厳密なエイリアシング」)のルールは、この場合の例外を示します。

オブジェクトの格納値には、次のいずれかのタイプの左辺値式によってのみアクセスする必要があります

  • メンバーの中に前述のタイプの1つを含む集合体または共用体タイプ

たとえば、最適化中に、関数を呼び出すコンパイラは、他の変換ユニットで定義されvoid func (B* b)た外部リネージ変数extern A a;が関数によって変更されなかったと想定することはできません。