web-dev-qa-db-ja.com

ゼロ要素の配列の必要性は何ですか?

Linuxカーネルコードで、理解できない次のことがわかりました。

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

コードはこちら: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

要素がゼロのデータ配列の必要性と目的は何ですか?

115
Jeegar Patel

これは、malloc(この場合はkmalloc)を2回呼び出すことなく、可変サイズのデータ​​を保持する方法です。次のように使用します。

_struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);
_

これは以前は標準ではなく、ハッキングと見なされていました(Aniketが言ったように)が、それはC99で標準化でした。現在の標準形式は次のとおりです。

_struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */
_

dataフィールドのサイズには言及していないことに注意してください。また、この特殊変数は構造体の最後にしか来ないことに注意してください。


C99では、この問題は6.7.2.1.16(強調鉱山)で説明されています。

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

または言い換えれば、あなたが持っている場合:

_struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);
_

_var->data_のインデックスを使用して、_[0, extra)_にアクセスできます。 sizeof(struct something)は、他の変数を考慮したサイズのみを提供します。つまり、dataのサイズは0になります。


標準が実際にmallocingのそのような構造の例をどのように提供するかに注意することも興味深いかもしれません(6.7.2.1.17):

_struct s { int n; double 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のオフセットは同じではないかもしれません)。

131
Shahbaz

これは、実際には [〜#〜] gcc [〜#〜]C9 )のハックです。

struct hack とも呼ばれます。

だから次回は、私は言うだろう:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);

それは次のように言うことと同等です:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};

そして、そのようなstructオブジェクトをいくつでも作成できます。

36
Aniket Inge

この考え方は、構造体の最後に可変サイズの配列を許可することです。おそらく、bts_actionは、固定サイズのヘッダー(typeおよびsizeフィールド)と可変サイズのdataメンバーを持つデータパケットです。長さ0の配列として宣言することにより、他の配列と同様にインデックスを作成できます。次に、次のように、たとえば1024バイトのdataサイズのbts_action構造体を割り当てます。

size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);

参照: http://c2.com/cgi/wiki?StructHack

7
sheu

コードは有効なCではありません( これを参照 )。 Linuxカーネルは、明らかな理由で、移植性に少しも関係していないため、多くの非標準コードを使用しています。

彼らがしているのは、配列サイズ0のGCC非標準拡張です。標準準拠のプログラムはu8 data[];そして、それはまったく同じことを意味するでしょう。 Linuxカーネルの作成者は、そうするためのオプションが明らかになれば、不必要に複雑で非標準的なものにすることを好むようです。

古いC標準では、空の配列で構造体を終了することは「構造体ハック」として知られていました。他の回答ですでにその目的を説明している人もいます。 C90標準の構造体ハックは未定義の動作であり、主にCコンパイラが構造体の最後に任意の数のパディングバイトを追加できるため、クラッシュを引き起こす可能性がありました。このようなパディングバイトは、構造体の最後で「ハッキング」しようとしたデータと衝突する可能性があります。

GCCは、これを未定義の動作から明確に定義された動作に変更するために、非標準の拡張機能を早期に作成しました。 C99規格はこの概念を採用しており、したがって、最新のCプログラムはリスクなしでこの機能を使用できます。 C99/C11ではflexible array memberとして知られています。

5
Lundin

あまり頻繁に見られない長さゼロの配列のもう1つの使用法は、構造体内で名前付きラベルを取得することです。

大きな構造体定義(複数のキャッシュラインにまたがる)があり、それらが最初と境界を横切る中間の両方でキャッシュラインの境界に合わせられるようにしたいとします。

struct example_large_s
{
    u32 first; // align to CL
    u32 data;
    ....
    u64 *second;  // align to second CL after the first one
    ....
};

コードでは、次のようなGCC拡張機能を使用して宣言できます。

__attribute__((aligned(CACHE_LINE_BYTES)))

ただし、実行時にこれを強制する必要があります。

ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);

これは単一の構造体で機能しますが、多くの構造体をカバーするのは難しく、各構造体は異なるメンバー名に揃えられます。ほとんどの場合、各構造体の最初のメンバーの名前を見つける必要がある次のようなコードを取得します。

assert (offsetof (one_struct,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);

この方法ではなく、構造体で長さゼロの配列を宣言して、一貫した名前の名前付きラベルとして機能しますが、スペースを消費しません。

#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
    CACHE_LINE_ALIGN_MARK (cacheline0);
    u32 first; // align to CL
    u32 data;
    ....
    CACHE_LINE_ALIGN_MARK (cacheline1);
    u64 *second;  // align to second CL after the first one
    ....
};

次に、ランタイムアサーションコードの保守がはるかに簡単になります。

assert (offsetof (one_struct,     cacheline0) == 0);
assert (offsetof (one_struct,     cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);
1
Wei Shen