34
  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
4

3 に答える 3

26

短い答え

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

  2. 可能ですが、可能性は低いです。実際に未定義の動作に達していなくても、厳密な準拠に失敗する可能性は依然としてあります。

  3. いいえ。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 要素の値に関する仮定はすべて偽になります。つまり、どのような仮定も、厳密な準拠に失敗することにつながります。

未定義の動作

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

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

于 2017-06-25T11:01:29.430 に答える