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


122

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

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


長さゼロの配列またはstruct-hackタグのどちらが必要かは
わかりませ

@hippietrail。誰かがこの構造体について尋ねると、「柔軟な配列メンバー」と呼ばれていることを知らないことがよくあります。もしそうなら、彼らは簡単に答えを見つけることができたでしょう。彼らはそうしないので、彼らはそのように質問にタグを付けることができません。そのため、そのようなタグはありません。
Shahbaz 2013

10
再開するための投票。他のどの投稿も長さがゼロの非標準の「構造体ハック」と明確に定義されたC99機能の柔軟な配列メンバーの組み合わせに対処していないため、これは重複ではなかったことに同意します。また、Cプログラミングコミュニティにとっては、Linuxカーネルのあいまいなコードに光を当てることが常に有益だと思います。主に多くの人が、理由は不明ですが、Linuxカーネルはある種の最先端のCコードであるという印象を持っています。実際には、それは一部のCカノンと見なされるべきではない、非標準的なエクスプロイトが殺到した恐ろしい混乱です。
ランディン2013

5
重複ではありません-誰かが不必要に質問を閉じるのを見たのはこれが初めてではありません。また、この質問はSOナレッジベースに追加されると思います。
Aniket Inge 2013

回答:


139

これは、mallockmallocこの場合)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のオフセットは同じではない可能性があります)。


明確にするために、問題の元のコードはC99(またはC11)ではまだ標準ではなく、ハックと見なされます。C99の標準化では、配列の境界を省略する必要があります。
MM

なに[0, extra)
SS Anne、

2
@ JL2210、en.wikipedia.org
Shahbaz

36

これは実際にはGCCC90)にとってハックです。

これはstruct hackとも呼ばれます。

次回はこう言います:

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

それは言うことと同等になります:

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

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


7

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

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

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


2
@Aniket:その考えがどこから来たのか、完全にはわかりません。
sheu

C ++では、はい、Cでは必要ありません。
amc 2013

2
@sheu、それはあなたの書き方のスタイルmallocがあなた自身を何度も繰り返すようにし、そしてどんなタイプのaction変更であっても、それを何度も修正しなければならないという事実から来ています。あなた自身のために次の二つを比較して、あなたが知っている:struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));struct some_thing *variable = malloc(10 * sizeof(*variable));秒1は、短くクリーンで明確に変更する方が簡単です。
Shahbaz 2013

5

コードは有効なCではありません(これを参照してください)。Linuxカーネルは、明らかな理由から、移植性をほとんど考慮していないため、非標準コードをたくさん使用しています。

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

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

初期のGCCは、これを未定義から明確な動作に変更する非標準の拡張を行いました。C99標準はこの概念を採用したため、最新のCプログラムはこの機能をリスクなしで使用できます。C99 / C11ではフレキシブルアレイメンバーとして知られています。


3
「Linuxカーネルは移植性に関係していない」とは思えません。おそらく、他のコンパイラへの移植性を意味していましたか?それがgccの機能とかなり絡み合っているのは事実です。
Shahbaz 2013

3
それにもかかわらず、この特定のコードは主流のコードではなく、作成者があまり注意を払っていなかったため、おそらく除外されていると思います。このライセンスでは、いくつかのテキサスインスツルメンツのドライバーについて説明しているため、カーネルのコアプログラマーがそれに注意を払った可能性はほとんどありません。カーネル開発者は常に新しいコードや新しい最適化に従って古いコードを更新していると確信しています。すべてが更新されていることを確認するには大きすぎます!
Shahbaz 2013

1
@Shahbaz「明白な」部分は、他の運用システムへの移植性を意味しましたが、当然のことながら意味がありません。しかし、それらは他のコンパイラーへの移植性についても気にしないようで、Linuxが他のコンパイラーに移植されることはないほど多くのGCC拡張機能を使用しています。
ランディン2013

3
@Shahbaz Texas Instrumentsというラベルの付いたものについては、TI自体が、さまざまなTIチップのアプリノートで、これまでに見た中で最も役に立たない、くだらない、素朴なCコードを生成することで悪名高くなっています。コードがTIからのものである場合、そこから役立つものを解釈する可能性に関するすべての賭けは無効になります。
ランディン2013

4
linuxとgccは切り離せないのは事実です。Linuxカーネルも理解が非常に困難です(主にOSが複雑であるためです)。私のポイントは、しかし、サードパーティのような悪いコーディング慣習のために、「Linuxカーネルの作者は、オプションを明らかにする場合、不必要に複雑で非標準にすることが好きなようです」と言うのは良くないということでした。 。
Shahbaz 2013

1

長さゼロの配列のもう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);

面白いアイデア。長さ0の配列は標準では許可されていないので、これはコンパイラ固有のものです。また、少なくとも構造体定義で長さ0の配列の動作に関するgccの定義を引用することをお勧めします。少なくとも、宣言の前または後にパディングを導入できるかどうかを示すためです。
Shahbaz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.