web-dev-qa-db-ja.com

構造体の最後のサイズ0の配列

私が受講しているシステムプログラミングコースの教授は、今日、最後に長さゼロの配列を持つ構造体を定義するように言っています。

_struct array{
    size_t size;
    int data[0];
};

typedef struct array array;
_

これは、変数を使用して配列を定義または初期化するのに便利な構造体です。つまり、次のようなものです。

_array *array_new(size_t size){
    array* a = malloc(sizeof(array) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}
_

つまり、malloc()を使用して、サイズ0の配列にもメモリを割り当てます。これは私にとってまったく新しいものであり、奇妙に思えます。なぜなら、私の理解から、構造体は必ずしも連続した場所に要素を持たないからです。

_array_new_のコードがメモリを_data[0]_に割り当てるのはなぜですか?なぜアクセスするのが合法なのか、と言う

_array * a = array_new(3);
a->data[1] = 12;
_

彼が私たちに言ったことから、構造体の最後の長さゼロとして定義された配列は、構造体の最後の要素の直後に確実に来るように見えますが、これは奇妙なようです。パディング。

また、これはgccの単なる機能であり、標準によって定義されていないことも見ました。これは本当ですか?

34
nbro

現在、C11の6.7.2.1章で述べられているように、フレキシブルアレイメンバーと呼ばれる標準機能があります。

標準を引用して、

特別な場合として、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型を持つ場合があります。これは、柔軟な配列メンバーと呼ばれます。ほとんどの場合、柔軟な配列メンバーは無視されます。特に、構造体のサイズは、省略可能が意味するよりも多くの後続パディングがある場合を除き、柔軟な配列メンバーが省略された場合と同じです。 [...]

構文は

struct s { int n; double d[]; };

ここで、最後の要素は不完全なタイプです。(配列の次元なし、0でもありません)。

したがって、コードは次のようになります。

struct array{
    size_t size;
    int data[ ];
};

標準に準拠しています。

さて、0サイズの配列の例を見てみましょう。これは、同じことを達成するための従来の方法( "struct hack")でした。前 C99GCCはこれを拡張機能としてサポートしていました 柔軟な配列メンバー機能をエミュレートします。

37
Sourav Ghosh

あなたの教授は混乱しています。 サイズがゼロの配列を定義するとどうなるか を読んでください。これは非標準のGCC拡張機能です。これは有効なCではなく、学生に使用を教えるべきものではありません(*)。

代わりに、標準のC柔軟な配列メンバーを使用してください。サイズがゼロの配列とは異なり、移植性があり、実際に機能します。

struct array{
    size_t size;
    int data[];
};

柔軟な配列メンバーは、構造体でsizeofを使用するとゼロとしてカウントされることが保証されており、次のようなことができます。

malloc(sizeof(array) + sizeof(int[size]));

(*)90年代に、人々は「struct hack」として知られる構造体の後にデータを追加するために安全でないエクスプロイトを使用しました。構造体を安全に拡張する方法を提供するために、GCCはゼロサイズの配列機能を非標準の拡張機能として実装しました。 1999年にC標準が最終的にこれを実現するためのより良い方法を提供したときに廃止されました。

24
Lundin

他の回答では、長さゼロの配列はGCC拡張であり、Cは可変長配列を許可していますが、他の質問には誰も答えていません。

私の理解では、構造体は必ずしも連続した場所に要素を持っているわけではありません。

はい。 structデータ型の要素は必ずしも連続した場所にあるとは限りません。

array_newのコードがメモリをdata[0]に割り当てるのはなぜですか?なぜアクセスするのが合法なのか、と言う

array * a = array_new(3);
a->data[1] = 12;

長さゼロの配列の制限の1つは、構造体の最後のメンバーでなければならないということです。これにより、コンパイラは構造体が可変長オブジェクトを持つことができ、実行時にさらに多くのメモリが必要になることを認識します。
しかし、混同しないでください。 "長さゼロの配列は構造の最後のメンバーであるため、長さゼロの配列に割り当てられたメモリを構造の最後に追加する必要があります。割り当てられたメモリにアクセスしますか? "

いいえ。そうではありません。構造体メンバーのメモリ割り当ては必ずしも連続しているわけではなく、それらの間にパディングがある場合がありますが、その割り当てられたメモリには変数dataを使用してアクセスする必要があります。はい、パディングはここでは効果がありません。ルールは次のとおりです:§6.7.2.1/ 15

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


また、これはgccの単なる機能であり、標準によって定義されていないことも見ました。これは本当ですか?

はい。他の回答で既に述べたように、長さゼロの配列は標準Cではサポートされていませんが、GCCコンパイラの拡張機能です。 C99が導入されました柔軟な配列メンバー。 C標準(6.7.2.1)の例:

宣言後:

struct s { int n; double d[]; };

構造体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のオフセットは同じではないかもしれません)。

7
haccks

より標準的な方法は、次のように1のデータサイズで配列を定義することです。

struct array{
    size_t size;
    int data[1]; // <--- will work across compilers
};

次に、計算で(配列のサイズではなく)データメンバーのoffsetを使用します。

array *array_new(size_t size){
    array* a = malloc(offsetof(array, data) + size * sizeof(int));

    if(a){
        a->size = size;
    }

    return a;
}

これは、余分なデータがどこに行くかを示すマーカーとしてarray.dataを効果的に使用しています(サイズによって異なります)。

1
Neil

構造体の最後にダミーメンバーを使用しない方法です。構造体のサイズそれ自体は、その直後のアドレスを示します。型付きポインターに1を追加するとそこに行きます:

header * p = malloc (sizeof (header) + buffersize);
char * buffer = (char*)(p+1);

一般的な構造体については、canフィールドが順番に配置されていることを知っています。ファイル形式のバイナリイメージ、オペレーティングシステムコール、またはハードウェアに必要な課された構造に一致できることは、Cを使用する利点の1つです。アライメントのパディングがどのように機能するかを知る必要がありますが、それらは順番に1つの連続したブロックにあります。

1
JDługosz