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
要素がゼロのデータの配列の必要性と目的は何ですか?
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
要素がゼロのデータの配列の必要性と目的は何ですか?
回答:
これは、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で説明されています(強調は私のものです):
特殊なケースとして、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型になる場合があります。これはフレキシブルアレイメンバーと呼ばれます。ほとんどの場合、フレキシブル配列メンバーは無視されます。特に、構造のサイズは、省略された場合より多くの末尾のパディングがある場合を除いて、フレキシブルアレイメンバーが省略されたかのようになります。ただし、。(または->)演算子には、柔軟な配列メンバーを持つ構造体(へのポインター)である左側のオペランドがあり、右側のオペランドにはそのメンバーの名前が付けられます。 )アクセスされるオブジェクトよりも構造が大きくなることはありません。配列のオフセットは、置換配列のオフセットと異なる場合でも、フレキシブル配列メンバーのオフセットのままにします。この配列に要素がない場合、
または言い換えれば、次の場合:
struct something
{
/* other variables */
char data[];
}
struct something *var = malloc(sizeof(*var) + extra);
のvar->data
インデックスを使用してアクセスできます[0, extra)
。sizeof(struct something)
は、他の変数を考慮したサイズのみを与えることに注意してくださいdata
。
規格が実際にそのmalloc
ような構成要素(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]));
同じ場所にある標準によるもう1つの興味深い注意事項は(強調は私のものです):
mallocの呼び出しが成功したとすると、pが指すオブジェクトは、ほとんどの目的で、pが次のように宣言されているかのように動作します。
struct { int n; double d[m]; } *p;
(この同等性が破られる状況があります。特に、メンバーdのオフセットは同じではない可能性があります)。
[0, extra)
?
これはstruct hackとも呼ばれます。
次回はこう言います:
struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
それは言うことと同等になります:
struct bts_action{
u16 type;
u16 size;
u8 data[100];
};
そして、そのような構造体オブジェクトをいくつでも作成できます。
考え方は、構造体の最後に可変サイズの配列を許可することです。おそらく、bts_action
固定サイズのヘッダー(the type
およびsize
fields)と可変サイズのdata
メンバーを持つデータパケットです。長さ0の配列として宣言することにより、他の配列と同じようにインデックスを付けることができます。次にbts_action
、たとえば1024バイトのdata
サイズの構造体を次のように割り当てます。
size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);
malloc
があなた自身を何度も繰り返すようにし、そしてどんなタイプのaction
変更であっても、それを何度も修正しなければならないという事実から来ています。あなた自身のために次の二つを比較して、あなたが知っている:struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));
対struct some_thing *variable = malloc(10 * sizeof(*variable));
秒1は、短くクリーンで明確に変更する方が簡単です。
コードは有効なCではありません(これを参照してください)。Linuxカーネルは、明らかな理由から、移植性をほとんど考慮していないため、非標準コードをたくさん使用しています。
彼らがしていることは、配列サイズが0のGCC非標準拡張です。標準に準拠したプログラムが作成されu8 data[];
ていれば、まったく同じことになります。Linuxカーネルの作者は、オプションが明らかになれば、不必要に複雑で非標準的なものを作ることを好むようです。
古いC標準では、空の配列で構造体を終了することは「構造体ハック」と呼ばれていました。他の人はすでに他の答えでその目的を説明しています。C90標準では、構造体ハックは未定義の動作であり、主にCコンパイラが構造体の末尾に任意の数のパディングバイトを自由に追加できるため、クラッシュを引き起こす可能性がありました。このようなパディングバイトは、構造体の最後で「ハッキング」しようとしたデータと衝突する可能性があります。
初期のGCCは、これを未定義から明確な動作に変更する非標準の拡張を行いました。C99標準はこの概念を採用したため、最新のCプログラムはこの機能をリスクなしで使用できます。C99 / C11ではフレキシブルアレイメンバーとして知られています。
長さゼロの配列のもう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);
この方法の代わりに、構造体で長さ0の配列を宣言して、一貫した名前を持つ名前付きラベルとして機能させることができますが、スペースを消費しません。
#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);