web-dev-qa-db-ja.com

柔軟な配列メンバーは未定義の動作につながる可能性がありますか?

  1. 構造体タイプ内でフレキシブル配列メンバー(FAM)を使用することにより、プログラムを未定義の動作の可能性にさらしていますか?

  2. プログラムがFAMを使用し、それでも厳密に準拠したプログラムである可能性はありますか?

  3. フレキシブル配列メンバーのオフセットは、構造体の最後にある必要がありますか?

質問はC99 (TC3)C11 (TC1)の両方に当てはまります。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    printf("sizeof *s: %zu\n", sizeof *s);
    printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));

    s->array[0] = 0;
    s->len = 1;

    printf("%d\n", s->array[0]);

    free(s);
    return 0;
}

出力:

sizeof *s: 16
offsetof(struct s, array): 12
0
34
Dror K.

短い答え

  1. はい。 FAMを使用する一般的な規則により、プログラムは未定義の動作の可能性にさらされます。そうは言っても、私は、誤動作する可能性のある既存の適合実装を知りません。

  2. 可能ですが、ありそうにありません。実際に未定義の動作に到達しなくても、厳密な準拠に失敗する可能性があります。

  3. No。 FAMのオフセットは、構造体の最後にある必要はありません。末尾のパディングバイトをオーバーレイできます。

答えはC99 (TC3)C11 (TC1)の両方に当てはまります。


長い答え

FAMはC99(TC0)(1999年12月)で最初に導入され、元の仕様ではFAMのオフセットを構造体の最後にする必要がありました。元の仕様は明確に定義されていたため、未定義の動作につながることも、厳密な適合性に関する問題になることもありませんでした。

C99 (TC0) §6.7.2.1 p16(1999年12月)

[このドキュメントは公式の標準であり、著作権で保護されており、無料で入手することはできません]

問題は、GCCなどの一般的なC99実装が標準の要件に準拠しておらず、FAMが末尾のパディングバイトをオーバーレイできることでした。彼らのアプローチはより効率的であると考えられ、標準の要件に従うためには下位互換性が失われるため、委員会は仕様を変更することを選択し、C99 TC2(2004年11月)以降、標準は不要になりました。構造体の最後にあるFAMのオフセット。

C99 (TC2) §6.7.2.1 p16(2004年11月)

[...]構造のサイズは、柔軟な配列メンバーが省略されているかのようですが、省略が意味するよりも多くの末尾のパディングがある場合があります。

新しい仕様では、FAMのオフセットを構造体の最後にする必要があるステートメントが削除され、非常に残念な結果がもたらされました。これは、標準により、構造内のパディングバイトの値を保持しない自由が実装に与えられているためです。一貫した状態の組合。すなわち:

C99 (TC3) §6.2.6.1 p6

メンバーオブジェクトを含む構造体または共用体タイプのオブジェクトに値が格納されている場合、パディングバイトに対応するオブジェクト表現のバイトは不特定の値を取ります。

これは、FAM要素のいずれかが末尾のパディングバイトに対応(またはオーバーレイ)する場合、構造体のメンバーに格納するときに、指定されていない値をとる可能性があることを意味します。これがFAM自体に格納されている値に適用されるかどうかを考える必要はありません。これは、FAM以外のメンバーにのみ適用されるという厳密な解釈でさえ、十分に損害を与えます。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    if (sizeof *s > offsetof(struct s, array)) {
        s->array[0] = 123;
        s->len = 1; /* any padding bytes take unspecified values */

        printf("%d\n", s->array[0]); /* indeterminate value */
    }

    free(s);
    return 0;
}

構造体のメンバーに格納すると、パディングバイトは不特定のバイトを受け取るため、後続のパディングバイトに対応するFAM要素の値について行われた仮定はすべてfalseになります。つまり、どんな仮定も私たちが厳密な適合に失敗することにつながるということです。

未定義の振る舞い

パディングバイトの値は「不特定の値」ですが、不特定の値に基づくオブジェクト表現はトラップ表現を生成する可能性があるため、パディングバイトの影響を受けるタイプについては同じことが言えません。したがって、これら2つの可能性を説明する唯一の標準的な用語は、「不確定な値」です。 FAMのタイプにトラップ表現がある場合、それにアクセスすることは、値が指定されていないことだけでなく、動作が定義されていないことの問題です。

しかし、待ってください、もっとあります。そのような値を説明する唯一の標準用語が「不確定な値」であることに同意する場合、FAMのタイプにトラップ表現がない場合でも、Cの公式解釈以来、未定義の動作に到達しています。標準委員会は、不確定な値を標準ライブラリ関数に渡すことは未定義の動作であるということです。

26
Dror K.

これはトリッキーなトピックを幅広く扱っている長い答えです。

TL; DR

analysis by Dror K に同意しません。

重要な問題は、C99およびC11標準の§6.2.1¶6が何を意味するのかを誤解し、それを次のような単純な整数割り当てに不適切に適用することです。

_fam_ptr->nonfam_member = 23;
_

この割り当ては、_fam_ptr_が指す構造体のパディングバイトを変更することは許可されていません。したがって、これにより構造内のパディングバイトが変更される可能性があるという推定に基づく分析は誤りです。

バックグラウンド

原則として、私はC99標準とそのコリジェンダについてひどく心配していません。それらは現在の標準ではありません。ただし、柔軟な配列メンバーの仕様の進化は有益です。

C99標準— ISO/IEC 9899:1999 —には3つの技術的基準がありました。

  • 2001-09-01(7ページ)にリリースされたTC1、
  • 2004年11月15日にリリースされたTC2(15ページ)、
  • 2007年11月15日にリリースされたTC3(10ページ)。

たとえば、gets()が廃止されて非推奨になり、C11標準から削除されたと述べたのはTC3でした。

C11標準— ISO/IEC 9899:2011 —には1つの技術的正誤表がありますが、それは単に2つのマクロの値を誤って_201ymmL_—値の形式で設定します。 ___STDC_VERSION___および___STDC_LIB_EXT1___に必要な値が値_201112L_に修正されました。 (TC1 —正式には「ISO/IEC9899:2011/Cor.1:2012(en)情報技術—プログラミング言語— C技術正誤表1」— https://www.iso.org/obp/ui /#iso:std:iso-iec:9899:ed-3:v1:cor:1:v1:en 。ダウンロード方法はわかりませんが、とてもシンプルなので、それほど重要ではありません。

柔軟な配列メンバーに関するC99標準

ISO/IEC 9899:1999(TC2より前)§6.7.2.1¶16:

特別な場合として、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型を持つ場合があります。これは柔軟な配列メンバーと呼ばれます。 2つの例外を除いて、フレキシブル配列メンバーは無視されます。まず、構造体のサイズは、フレキシブル配列メンバーを指定されていない長さの配列に置き換える、他の点では同一の構造体の最後の要素のオフセットに等しくなければなりません。106) 次に、_._(または_->_)演算子に、柔軟な配列メンバーを持つ構造体(へのポインター)である左オペランドがあり、右オペランドがそのメンバーに名前を付けると、そのメンバーのように動作します。アクセスされているオブジェクトよりも構造が大きくならない最長の配列(同じ要素タイプ)に置き換えられました。配列のオフセットは、置換配列のオフセットと異なる場合でも、フレキシブル配列メンバーのオフセットのままでなければなりません。この配列に要素がない場合、要素が1つあるかのように動作しますが、その要素にアクセスしたり、その要素の1つ先にポインターを生成したりしようとすると、動作は定義されません。

126) 実装が配列メンバーに長さに応じて異なる配置を与える可能性があるという事実を考慮して、長さは指定されていません。

(この脚注は書き直しで削除されています。)元のC99標準には1つの例が含まれていました。

¶17例宣言の後、すべての配列メンバーが同じように整列されていると仮定します。

_struct s { int n; double d[]; };
struct ss { int n; double d[1]; };
_

3つの表現:

_sizeof (struct s)
offsetof(struct s, d)
offsetof(struct ss, d)
_

同じ値を持っています。構造体structsには、柔軟な配列メンバーdがあります。

¶18sizeof(double)が8の場合、次のコードが実行された後:

_struct s *s1;
struct s *s2;
s1 = malloc(sizeof (struct s) + 64);
s2 = malloc(sizeof (struct s) + 46);
_

また、mallocの呼び出しが成功したとすると、s1とs2が指すオブジェクトは、識別子が次のように宣言されているかのように動作します。

_struct { int n; double d[8]; } *s1;
struct { int n; double d[5]; } *s2;
_

¶19さらに成功した割り当てに従う:

_s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);
_

その後、宣言が次のように動作します。

_struct { int n; double d[1]; } *s1, *s2;
_

そして:

_double *dp;
dp = &(s1->d[0]); // valid
*dp = 42;         // valid
dp = &(s2->d[0]); // valid
*dp = 42;         // undefined behavior
_

¶20割り当て:

_*s1 = *s2;
_

メンバーnのみをコピーし、配列要素はコピーしません。同様に:

_struct s t1 = { 0 };          // valid
struct s t2 = { 2 };          // valid
struct ss tt = { 1, { 4.2 }}; // valid
struct s t3 = { 1, { 4.2 }};  // invalid: there is nothing for the 4.2 to initialize
t1.n = 4;                     // valid
t1.d[0] = 4.2;                // undefined behavior
_

このサンプル資料の一部はC11で削除されました。例は規範的ではないため、TC2では変更は記録されていません(そして記録する必要はありませんでした)。しかし、C11で書き直された資料は、調査すると有益です。

フレキシブルアレイメンバーの問題を特定するN983ペーパー

N98 WG14から Pre-Santa Cruz-2002郵送 は、欠陥報告の最初の声明であると私は信じています。一部のCコンパイラ(3つを引用)は、構造の最後のパディングの前にFAMを配置することができます。最終的な欠陥レポートは DR 282 でした。

私が理解しているように、このレポートはTC2の変更につながりましたが、プロセスのすべてのステップを追跡したわけではありません。 DRは個別に利用できなくなったようです。

TC2は、規範資料のC11標準にある表現を使用しました。

柔軟な配列メンバーに関するC11標準

では、C11標準は柔軟な配列メンバーについて何と言っているのでしょうか。

§6.7.2.1構造体と共用体の指定子

¶3構造体または共用体は、不完全または関数型のメンバーを含むことはできません(したがって、構造体はそれ自体のインスタンスを含むことはできませんが、それ自体のインスタンスへのポインターを含むことができます)。複数の名前付きメンバーの配列タイプが不完全である可能性があります。そのような構造体(および、おそらく再帰的に、そのような構造体であるメンバーを含むユニオン)は、構造体のメンバーまたは配列の要素であってはなりません。

これにより、FAMが構造の最後にしっかりと配置されます—「最後のメンバー」は定義上、構造の最後にあり、これは次のようにして確認されます。

¶15構造体オブジェクト内で、非ビットフィールドメンバーとビットフィールドが存在するユニットは、宣言された順序で増加するアドレスを持っています。

¶17構造体または共用体の末尾に名前のないパディングがある場合があります。

¶18特別な場合として、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型を持つ場合があります。これは柔軟な配列メンバーと呼ばれます。ほとんどの場合、フレキシブル配列メンバーは無視されます。特に、構造のサイズは、省略が意味するよりも多くの末尾のパディングがある場合を除いて、フレキシブルアレイメンバーが省略されたかのようになります。ただし、_._(または_->_)演算子に、柔軟な配列メンバーを持つ構造体(へのポインター)である左オペランドがあり、右オペランドがそのメンバーに名前を付ける場合、そのメンバーのように動作します。アクセスされているオブジェクトよりも構造が大きくならない最長の配列(同じ要素タイプ)に置き換えられました。配列のオフセットは、置換配列のオフセットと異なる場合でも、フレキシブル配列メンバーのオフセットのままでなければなりません。この配列に要素がない場合、要素が1つあるかのように動作しますが、その要素にアクセスしたり、その要素の1つ先にポインターを生成したりしようとすると、動作は定義されません。

この段落には、ISO/IEC 9899:1999/Cor.2:2004(E)の¶20の変更が含まれています—C99のTC2。

柔軟な配列メンバーを含む構造体の主要部分の最後にあるデータは、任意の構造体タイプで発生する可能性のある通常の末尾のパディングです。このようなパディングに合法的にアクセスすることはできませんが、未定義の動作を発生させることなく、構造体へのポインターを介してライブラリ関数などに渡すことができます。

C11標準には3つの例が含まれていますが、1番目と3番目は、柔軟な配列メンバーのメカニズムではなく、匿名の構造体と共用体に関連しています。例は「規範的」ではありませんが、例示的なものであることを忘れないでください。

¶20例2宣言後:

_struct s { int n; double d[]; };
_

構造体_struct s_には、柔軟な配列メンバーdがあります。これを使用する一般的な方法は次のとおりです。

_int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
_

mallocの呼び出しが成功したとすると、pが指すオブジェクトは、ほとんどの目的で、pが次のように宣言されているかのように動作します。

_struct { int n; double d[m]; } *p;
_

(この同等性が破られる状況があります。特に、メンバーdのオフセットが同じでない場合があります)。

¶21上記の宣言に従う:

_struct s t1 = { 0 };          // valid
struct s t2 = { 1, { 4.2 }};  // invalid
t1.n = 4;                     // valid
t1.d[0] = 4.2;                // might be undefined behavior
_

_t2_は、メンバーdが含まれていないかのように扱われるため、_struct s_の初期化は無効です(制約に違反します)。 _t1.d[0]_への割り当てはおそらく未定義の動作ですが、

_sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)
_

その場合、割り当ては正当になります。それにもかかわらず、厳密に準拠したコードでは表示できません。

¶22さらなる宣言の後:

_struct ss { int n; };
_

式:

_sizeof (struct s) >= sizeof (struct ss)
sizeof (struct s) >= offsetof(struct s, d)
_

常に1に等しい。

¶23sizeof (double)が8の場合、次のコードが実行された後:

_struct s *s1;
struct s *s2;
s1 = malloc(sizeof (struct s) + 64);
s2 = malloc(sizeof (struct s) + 46);
_

mallocの呼び出しが成功すると仮定すると、_s1_および_s2_が指すオブジェクトは、ほとんどの目的で、識別子が次のように宣言されているかのように動作します。

_struct { int n; double d[8]; } *s1;
struct { int n; double d[5]; } *s2;
_

¶24さらに成功した割り当てに従う:

_s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);
_

その後、宣言が次のように動作します。

_struct { int n; double d[1]; } *s1, *s2;
_

そして:

_double *dp;
dp = &(s1->d[0]); // valid
*dp = 42;         // valid
dp = &(s2->d[0]); // valid
*dp = 42;         // undefined behavior
_

¶25割り当て:

_*s1 = *s2;
_

メンバーnのみをコピーします。配列要素のいずれかが構造体の最初のsizeof (struct s)バイト内にある場合、それらはコピーされるか、不確定な値で単に上書きされる可能性があります。

これはC99とC11の間で変更されていることに注意してください。

標準の別の部分では、このコピー動作について説明しています。

§6.2.6タイプの表現§6.2.6.1一般

¶6メンバーオブジェクトを含む構造体または共用体タイプのオブジェクトに値が格納されている場合、パディングバイトに対応するオブジェクト表現のバイトは不特定の値を取ります。51) 構造体または共用体オブジェクトのメンバーの値がトラップ表現である場合でも、構造体または共用体オブジェクトの値がトラップ表現になることはありません。

51) したがって、たとえば、構造体の割り当てでは、パディングビットをコピーする必要はありません。

問題のあるFAM構造の説明

Cチャットルーム では、私は 書いた これが言い換えであるいくつかの情報:

考えてみましょう:

_struct fam1 { double d; char c; char fam[]; };
_

Doubleが8バイトのアラインメント(または4バイト。それほど重要ではありませんが、8を使用します)が必要であると仮定すると、_struct non_fam1a { double d; char c; };_にはcの後に7バイトのパディングがあります。サイズは16です。さらに、_struct non_fam1b { double d; char c; char nonfam[4]; };_には、nonfam配列の後に3バイトのパディングがあり、サイズは16です。

sizeof(struct fam1)が16であっても、_struct fam1_のfamの開始はオフセット9にある可能性があります。したがってcの後のバイトはそうではありません。パディング(必然的に)。

したがって、FAMが十分に小さい場合でも、構造体とFAMのサイズは_struct fam_のサイズよりも小さい可能性があります。

プロトタイプの割り当ては次のとおりです。

_struct fam1 *fam = malloc(sizeof(struct fam1) + array_size * sizeof(char));
_

fAMのタイプがcharの場合(_struct fam1_のように)。 famのオフセットがsizeof(struct fam1)より小さい場合、これは(全体的な)過大評価です。

Dror K。指摘

構造のサイズよりも小さいFAMオフセットに基づいて、「正確な」必要なストレージを計算するためのマクロがあります。このようなもの: https://gustedt.wordpress.com/2011/03/14/flexible-array-member/

質問への対応

質問は尋ねます:

  1. 構造体タイプ内でフレキシブル配列メンバー(FAM)を使用することにより、プログラムを未定義の動作の可能性にさらしていますか?
  2. プログラムがFAMを使用し、それでも厳密に準拠したプログラムである可能性はありますか?
  3. フレキシブル配列メンバーのオフセットは、構造体の最後にある必要がありますか?

質問は、C99(TC3)とC11(TC1)の両方に適用されます。

正しくコーディングすれば、答えは「いいえ」、「はい」、「いいえ、はい、状況によって異なります…」だと思います。

質問1

質問1の目的は、「どこかでFAMを使用する場合、プログラムは必然的に未定義の動作にさらされる必要があるか」ということだと思います。私が明白だと思うことを述べると、プログラムを未定義の動作にさらす方法はたくさんあります(そして、それらの方法のいくつかは、柔軟な配列メンバーを持つ構造を含みます)。

単にFAMを使用するということは、プログラムが自動的に未定義の動作をする(呼び出す、公開される)ことを意味するとは思いません。

質問2

セクション§4適合性は以下を定義します:

¶5厳密に準拠するプログラムは、この国際規格で指定されている言語とライブラリの機能のみを使用するものとします。3) 未指定、未定義、または実装定義の動作に依存する出力を生成してはならず、実装の最小制限を超えてはなりません。

3) 厳密に準拠するプログラムは、関連するマクロを使用する適切な条件付き包含前処理ディレクティブによって使用が保護されている場合、条件付き機能(6.10.8.3を参照)を使用できます。 …

¶7適合プログラムは、適合実装に受け入れられるプログラムです。5)

5) 厳密に準拠するプログラムは、準拠する実装間で最大限に移植できるようにすることを目的としています。適合プログラムは、適合実装の移植性のない機能に依存する場合があります。

標準Cの機能で、標準が意図する方法で使用すると、プログラムが厳密に準拠しなくなるような機能はないと思います。そのようなものがある場合、それらはロケール依存の動作に関連しています。 FAMコードの動作は、本質的にロケールに依存しません。

FAMの使用は、本質的にプログラムが厳密に準拠していないことを意味するとは思いません。

質問3

質問3は次の間であいまいだと思います。

  • 3A:フレキシブルアレイメンバーのオフセットは、フレキシブルアレイメンバーを含む構造のサイズと同じである必要がありますか?
  • 3B:フレキシブルアレイメンバーのオフセットは、構造の以前のメンバーのオフセットよりも大きくする必要がありますか?

3Aの答えは「いいえ」です(上記で引用した¶25のC11の例をご覧ください)。

3Bへの答えは「はい」です(上記で引用した証人§6.7.2.1¶15)。

Drorの答えに反対する

C標準とDrorの答えを引用する必要があります。 Drorの回答からの引用の開始を示すために_[DK]_を使用し、マークされていない引用はC標準からのものです。

2017-07-01 18:00 -08:00の時点で、Dror Kによる 短い回答 は次のように述べています。

_[DK]_

  1. はい。 FAMを使用する一般的な規則により、プログラムは未定義の動作の可能性にさらされます。そうは言っても、私は、誤動作する可能性のある既存の適合実装を知りません。

FAMを使用するだけで、プログラムの動作が自動的に未定義になるとは思いません。

_[DK]_

  1. 可能ですが、ありそうにありません。実際に未定義の動作に到達しなくても、厳密な準拠に失敗する可能性があります。

FAMを使用すると、プログラムが自動的に厳密に準拠しなくなるとは思いません。

_[DK]_

  1. いいえ。FAMのオフセットは構造体の最後にある必要はありません。末尾のパディングバイトをオーバーレイできます。

これが私の解釈3Aに対する答えであり、私はこれに同意します。

長い答えには、上記の短い答えの解釈が含まれています。

_[DK]_

問題は、GCCなどの一般的なC99実装が標準の要件に準拠しておらず、FAMが末尾のパディングバイトをオーバーレイできることでした。彼らのアプローチはより効率的であると考えられ、標準の要件に従うためには下位互換性が失われるため、委員会は仕様を変更することを選択し、C99 TC2(2004年11月)以降、標準は不要になりました。構造体の最後にあるFAMのオフセット。

私はこの分析に同意します。

_[DK]_

新しい仕様では、FAMのオフセットを構造体の最後にする必要があるステートメントが削除され、非常に残念な結果がもたらされました。これは、標準により、構造内のパディングバイトの値を保持しない自由が実装に与えられているためです。一貫した状態の組合。

新しい仕様により、FAMを構造のサイズ以上のオフセットで格納するという要件が削除されたことに同意します。

パディングバイトに問題があることに同意しません。

この規格では、FAMを含む構造の構造割り当ては、FAMを事実上無視すると明示的に規定されています(§6.7.2.1¶18)。非FAMメンバーをコピーする必要があります。パディングバイトはまったくコピーする必要がないと明示的に述べられています(§6.2.6.1¶6および脚注51)。また、例2は、FAMが構造によって定義されたスペースとオーバーラップする場合、構造の終わりとオーバーラップするFAMの部分からのデータは、そうである場合とそうでない場合があることを明示的に示しています(非規範的に§6.7.2.1¶25)。コピーされました。

_[DK]_

これは、FAM要素のいずれかが末尾のパディングバイトに対応(またはオーバーレイ)する場合、構造体のメンバーに格納するときに、指定されていない値をとる可能性があることを意味します。これがFAM自体に格納されている値に適用されるかどうかを考える必要はありません。これは、FAM以外のメンバーにのみ適用されるという厳密な解釈でさえ、十分に損害を与えます。

これは問題ではないと思います。構造体の割り当てを使用してFAMを含む構造体をコピーし、FAM配列をコピーできるという期待には本質的に欠陥があります。コピーすると、FAMデータは論理的にコピーされません。構造の範囲内でFAMデータに依存するプログラムはすべて壊れています。これは(欠陥のある)プログラムのプロパティであり、標準ではありません。

_[DK]_

_#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    if (sizeof *s > offsetof(struct s, array)) {
        s->array[0] = 123;
        s->len = 1; /* any padding bytes take unspecified values */

        printf("%d\n", s->array[0]); /* indeterminate value */
    }

    free(s);
    return 0;
}
_

もちろん、理想的には、コードは名前付きメンバーpadを確定値に設定しますが、アクセスされないため、実際には問題は発生しません。

printf()の_s->array[0]_の値が不確定であることに強く反対します。その値は_123_です。

以前の標準見積もりは次のとおりです(脚注番号はC99では42、C11では51ですが、C99とC11の両方で同じ§6.2.6.1¶6です)。

メンバーオブジェクトを含む構造体または共用体タイプのオブジェクトに値が格納されている場合、パディングバイトに対応するオブジェクト表現のバイトは不特定の値を取ります。

_s->len_は、構造体または共用体型のオブジェクトへの割り当てではないことに注意してください。これは、タイプ_size_t_のオブジェクトへの割り当てです。これがここでの混乱の主な原因かもしれないと思います。

コードに含まれている場合:

_struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
printf("t->array[0] = %d\n", t->array[0]);
_

その場合、出力される値は確かに不確定です。ただし、これは、FAMを使用して構造をコピーしても、FAMがコピーされるとは限らないためです。より正確なコードは次のようになります(もちろん、_#include <string.h>_を追加すると仮定します)。

_struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
memmmove(t->array, s->array, sizeof(t->array[0]));
printf("t->array[0] = %d\n", t->array[0]);
_

これで、出力される値は確定的です(_123_です)。 if (sizeof *s > offsetof(struct s, array))の条件は私の分析には重要ではないことに注意してください。

長い答えの残りの部分(主に「未定義の振る舞い」という見出しで識別されるセクション)は、構造体の整数メンバーに割り当てるときに構造体のパディングバイトが変更される可能性についての誤った推論に基づいているため、残りの部分は議論はさらに分析する必要はありません。

_[DK]_

構造体のメンバーに格納すると、パディングバイトは不特定のバイトを受け取るため、後続のパディングバイトに対応するFAM要素の値について行われた仮定はすべてfalseになります。つまり、どんな仮定も私たちが厳密な適合に失敗することにつながるということです。

これは誤った前提に基づいています。結論は誤りです。

19

厳密に準拠したプログラムがすべての正当な動作で「機能」する場合に実装定義の動作を利用できるようにする場合(ほとんどすべての種類の有用な出力は、実行文字セットなどの実装定義の詳細に依存しますが) )、プログラムがフレキシブル配列メンバーのオフセットが構造の長さと一致するかどうかを気にしないという条件で、厳密に準拠したプログラム内でのフレキシブル配列メンバーの使用が可能である必要があります。

配列は内部にパディングがあるとは見なされないため、FAMのために追加されるパディングはその前に配置されます。 FAMのメンバーを収容するのに十分なスペースが構造内または構造の外側にある場合、それらのメンバーはFAMの一部です。たとえば、次のようになります。

struct { long long x; char y; short z[]; } foo;

「foo」のサイズは、配置のためにzの先頭を超えてパディングされる場合がありますが、そのようなパディングはzの一部として使用できます。 yを書き込むと、zの前のパディングが妨げられる可能性がありますが、z自体のどの部分も妨げてはなりません。

7
supercat