C / C ++でサイズ0の配列を定義するとどうなりますか?


127

int array[0];コードで長さゼロの配列を定義すると、実際にはどうなりますか?GCCはまったく文句を言いません。

サンプルプログラム

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

明確化

私は実際に、Darhazerのコメントの可変長のように指されるのではなく、このように初期化された長さゼロの配列が最適化されているかどうかを把握しようとしています。

これは、一部のコードを実際にリリースする必要があるため、SIZEがとして定義されているケースを処理する必要があるかどうかを判断しようとし0ています。int array[SIZE];

私は実際にGCCが文句を言わないことに驚き、それが私の質問につながりました。私が受け取った回答から、警告がないのは、新しい[]構文で更新されていない古いコードをサポートしていることが主な原因であると思います。

私は主にエラーについて疑問に思っていたので、Lundinの答えに正しいタグを付けています(Nawazの答えが最初でしたが、完全ではありませんでした)。 tまさに私が探していたもの。


51
@AlexanderCorwin:残念ながら、C ++では、未定義の動作、非標準の拡張、その他の異常があるため、自分で何かを試すことは、多くの場合、知識への道ではありません。
ベンジャミンリンドレー

5
@JustinKirk私はそれをテストし、それが機能することを証明することによって、それによってもトラップされました。そして、私の投稿で受けた批判のせいで、私はそれをテストして動作させることがそれが有効で合法であるとは限らないことを学びました。したがって、セルフテストが有効でない場合があります。
StormByte

2
@JustinKirk、それを使用する場所の例については、Matthieuの回答を参照してください。また、配列サイズがテンプレートパラメータであるテンプレートでも役立ちます。問題の例は明らかに文脈外です。
Mark Ransom

2
@JustinKirk:[]Pythonまたは""Cの目的は何ですか?場合によっては、配列を必要とする関数またはマクロを持っているのに、データを入力できないことがあります。
dan04

15
「C / C ++」とは何ですか?これらは2つの異なる言語です
オービットのライトネスレース

回答:


86

配列のサイズをゼロにすることはできません。

ISO 9899:2011 6.7.6.2:

式が定数式の場合、その値はゼロより大きい値でなければなりません。

上記のテキストは、単純な配列(段落1)の両方に当てはまります。VLA(可変長配列)の場合、式の値がゼロ以下の場合の動作は未定義です(段落5)。これは、C標準の規範的なテキストです。コンパイラはそれを異なる方法で実装することはできません。

gcc -std=c99 -pedantic 非VLAの場合に警告を出します。


34
「実際にエラーを出さなければならない」-「警告」と「エラー」の区別は標準では認識されず(「診断」についてのみ言及されている)、コンパイルを停止する必要がある唯一の状況[つまり、実際の違い警告とエラーの間]は、#errorディレクティブに遭遇したときです。
Random832

12
参考までに、一般的な規則として、(CまたはC ++)標準では、コンパイラーが許可する必要があることのみを規定し、禁止する必要があるものは規定していませ。場合によっては、コンパイラが「診断」を発行する必要があると彼らは述べますが、それは彼らが得るのとほぼ同じくらい具体的です。残りはコンパイラベンダーに任されています。編集:Random832が言ったことも。
mcmcc 2012年

8
@Lundin「長さ0の配列を含むバイナリをコンパイラで構築することは許可されていません。」この規格では、まったくそのようなことは何も言われていません。そのサイズが長さゼロの定数式を持つ配列を含むソースコードが与えられた場合、少なくとも1つの診断メッセージを生成する必要があるとだけ述べています。標準がコンパイラーにバイナリーの作成を禁止する唯一の状況は、#errorプリプロセッサー・ディレクティブが検出された場合です。
Random832 2012年

5
@Lundinすべての正しいケースでバイナリを生成しても#1を満たし、不正なケースでバイナリを生成してもしなくても、影響はありません。#3は警告を出力するだけで十分です。標準ではこのソースコードの動作が定義されていないため、この動作は#2とは無関係です。
Random832 2012年

13
@Lundin:ポイントはあなたの発言が間違っているということです。適合コンパイラ、診断が発行される限り、長さ0の配列を含むバイナリを構築できます。
キーストンプソン

85

規格により許可されていません。

ただし、Cコンパイラでは現在、これらの宣言を柔軟な配列メンバー(FAM宣言として扱うことが慣例となっています。

C99 6.7.2.1、§16:特殊なケースとして、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型を持つ場合があります。これは、フレキシブルアレイメンバーと呼ばれます。

FAMの標準構文は次のとおりです。

struct Array {
  size_t size;
  int content[];
};

アイデアは、それを次のように割り当てるということです。

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

静的に使用することもできます(gcc拡張)。

Array a = { 3, { 1, 2, 3 } };

これは、テールパディングされた構造(この用語はC99標準の公開に先立つ)または構造ハック(それを指摘してくれたJoe Wreschnigに感謝)とも呼ばれます

ただし、この構文は標準化された(そして効果が保証された)のは最近C99でのみでした。以前は一定のサイズが必要でした。

  • 1 ポータブルな方法でしたが、かなり奇妙でした。
  • 0 意図を示すのは得意でしたが、標準に関する限り合法ではなく、一部のコンパイラ(gccを含む)によって拡張機能としてサポートされていました。

ただし、テールパディングの実践は、ストレージが利用可能(注意malloc)であることに依存しているため、一般的にスタックの使用には適しいません


@Lundin:ここではVLAを見たことがありません。すべてのサイズはコンパイル時にわかっています。柔軟な配列の用語から来てgcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.htmlとDOEの資格int content[];限り私は理解してここに。私はCの用語にあまり詳しくないので...私の推論が正しいように見えるかどうか確認してもらえますか?
Matthieu M.

@MatthieuM .: C99 6.7.2.1、§16:特殊なケースとして、複数の名前付きメンバーを持つ構造の最後の要素は、不完全な配列型を持つ場合があります。これは、フレキシブルアレイメンバーと呼ばれます。
クリストフ

このイディオムは「構造体ハック」という名前でも知られており、「テールパディング構造」よりもその名前に詳しい人に会ったことがあります(おそらく、将来のABI互換性のために構造体にパディングするための一般的な参照として以外に聞いたことはありません) )またはC99で最初に聞いた「柔軟な配列メンバー」。

1
構造体のハックに1の配列サイズを使用すると、コンパイラーが誤動作するのを回避できますが、コンパイラーの作成者は事実上の標準としてそのような使用法を認めるのに十分優れていたため、「移植性」のみでした。サイズが0の配列の禁止、プログラマーによる単一要素配列の不必要な代用としての使用、および標準で要求されていない場合でもプログラマーのニーズに応えるべきであるというコンパイラーライターの歴史的な態度のためではありませんでした。簡単かつ便利に最適化されfoo[x]foo[0]いつでもfoo単一要素の配列になります。
スーパーキャット2015

1
@RobertSsupportsMonicaCellio:回答で明示的に示されているとおりですが、最後にあります。説明もわかりやすくするために、説明もフロントロードしました。
Matthieu M.

58

標準CおよびC ++では、サイズがゼロの配列は許可されていません

GCCを使用している場合は、-pedanticオプションでコンパイルします。次のように警告します:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

C ++の場合、同様の警告が表示されます。


9
Visual C ++ 2010の場合:error C2466: cannot allocate an array of constant size 0
Mark Ransom

4
-Werrorはすべての警告をエラーに変換するだけであり、GCCコンパイラの誤った動作は修正されません。
ランディン

C ++ Builder 2009でもエラーが発生します。 [BCC32 Error] test.c(3): E2021 Array must have at least one element
ランディン

1
の代わりに-pedantic -Werror、あなたも行うことができます-pedantic-errors
ステファンドールバーグ

3
サイズがゼロの配列は、サイズがゼロの配列とまったく同じではありませんstd::array。(余談ですが、VLAが考慮され、C ++であることを明示的に拒否されたソースを見つけられません。)

27

それは完全に違法であり、常にそうでしたが、多くのコンパイラはエラーを通知することを怠っています。なぜこれをしたいのかわかりません。私が知っている1つの使用法は、ブール値からコンパイル時エラーをトリガーすることです。

char someCondition[ condition ];

conditionがfalseの場合、コンパイル時エラーが発生します。コンパイラはこれを許可しているので、しかし、私は使用することにしました:

char someCondition[ 2 * condition - 1 ];

これにより、1または-1のいずれかのサイズが得られます。私は、-1のサイズを受け入れるコンパイラーを見つけたことはありません。


これは、それを使用する興味深いハックです。
Alex Koay

10
それはメタプログラミングの一般的なトリックだと思います。の実装がSTATIC_ASSERTそれを使用したとしても、私は驚かないでしょう。
James Kanze

なぜだけではない:#if condition \n #error whatever \n #endif
Jerfov2

1
@ Jerfov2は、前処理時に条件がわからない可能性があるため、コンパイル時のみ
rmeador

9

あることを追加します この引数については、gccのオンラインドキュメントの全ページ

いくつかの引用:

GNU Cでは長さ0の配列を使用できます。

ISO C90では、コンテンツの長さを1にする必要があります。

そして

3.0より前のGCCバージョンでは、長さが0の配列を、柔軟な配列のように静的に初期化できました。有用なケースに加えて、後のデータを破壊するような状況での初期化も可能にしました

そうすることができます

int arr[0] = { 1 };

とブーム:-)


私は次のように行うことができint a[0]、その後、a[0] = 1 a[1] = 2??
Suraj Jain

2
@SurajJainスタックを上書きしたい場合:-) Cはインデックスと書き込み中の配列のサイズをチェックしないので、チェックできa[100000] = 5ますが、運が良ければ、アプリをクラッシュさせます(運が良ければ)。 -)
xanatos

Int a [0]; は可変配列(サイズがゼロの配列)を意味します。どうすれば割り当てられますか
Suraj Jain

@SurajJain「Cは、作成中の配列のサイズとインデックスの比較をチェックしません」のどの部分が明確ではありませんか?Cにはインデックスチェックがありません。配列の最後に書き込んでコンピューターをクラッシュさせるか、メモリの貴重なビットを上書きできます。したがって、0要素の配列がある場合、0要素の終わりの後に書き込むことができます。
xanatos


9

長さゼロの配列のもう1つの用途は、可変長オブジェクト(C99以前)を作成することです。長さゼロの配列は、0のない[]を持つフレキシブル配列とは異なります

gcc docから引用:

GNU Cでは長さ0の配列が許可されています。これらは実際には可変長オブジェクトのヘッダーである構造体の最後の要素として非常に役立ちます。

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

ISO C99では、柔軟な配列メンバーを使用します。これは、構文とセマンティクスが少し異なります。

  • 柔軟な配列メンバーは、0なしでcontent []として書き込まれます。
  • フレキシブルアレイメンバーの型は不完全であるため、sizeof演算子は適用されない場合があります。

実際の例はstruct kdbus_itemkdbus.h(Linuxカーネルモジュール)のゼロ長配列です。


2
私見、規格が長さゼロの配列を禁止する正当な理由はありませんでした。サイズ0のオブジェクトを構造体のメンバーとして問題なく持つことができ、それらvoid*を算術の目的と見なしました(そのため、サイズ0のオブジェクトへのポインターの加算または減算は禁止されています)。フレキシブルアレイメンバーは、ほとんどがサイズ0の配列よりも優れていますが、それらは、それに続く「構文」の間接的なレベルを追加することなく、エイリアスの一種の「ユニオン」として機能することもできます(たとえば、struct foo {unsigned char as_bytes[0]; int x,y; float z;}メンバーにアクセスできる場合)xz...
スーパーキャット2015

... myStruct.asFoo.xなどを言う必要なしに直接。さらに、IIRC、Cは、構造内にフレキシブルアレイメンバーを含めるために努力しているため、既知の長さの複数の他のフレキシブルアレイメンバーを含む構造を持つことは不可能です。コンテンツ。
スーパーキャット2015

@supercatの正当な理由は、配列の境界外へのアクセスに関するルールの整合性を維持することです。構造体の最後のメンバーとして、C99 フレキシブル配列メンバーは、GCCゼロサイズ配列とまったく同じ効果を実現しますが、他のルールに特別なケースを追加する必要はありません。私見それsizeof x->contentsはgccで0を返すのではなく、ISO Cのエラーである改善です。構造体メンバーではないサイズがゼロの配列は、他にも多くの問題を引き起こします。
MM

@MM:サイズがゼロのオブジェクトへの2つの等しいポインターを減算するとゼロを生成するように定義され(任意のサイズのオブジェクトへの等しいポインターを減算するように)、サイズがゼロのオブジェクトへの等しくないポインターを減算すると生成されると定義された場合、どのような問題が発生しますか?指定されていない値?実装がFAMを含む構造体を別の構造体に埋め込むことを標準が指定している場合、後者の構造体の次の要素がFAMと同じ要素型の配列またはそのような配列で始まる構造体である場合、そしてそれを提供しました...
スーパーキャット

... FAMを配列のエイリアスとして認識します(配置規則により配列が異なるオフセットに到達する場合、診断が必要になります)。これは非常に便利です。現状のままでは、一般的な形式の構造体へのポインタを受け入れstruct {int n; THING dat[];}、静的または自動継続時間のもので機能できるメソッドを用意する良い方法はありません。
スーパーキャット2016

6

構造体内のサイズ0の配列宣言は、それらが許可されていて、セマンティクスが(1)アラインメントを強制するが、それ以外の場合はスペースを割り当てない、および(2)配列のインデックス付けは、結果のポインタが構造体と同じメモリブロック内にある場合。このような動作はC標準では決して許可されていませんでしたが、一部の古いコンパイラでは、コンパイラが空の角かっこを使用した不完全な配列宣言を許可する標準になる前に許可されていました。

サイズ1の配列を使用して一般的に実装されるstruct hackは危険なものであり、コンパイラーがそれを壊さないようにする要件はないと思います。例えば、私は、コンパイラが見ればことを期待しint a[1]、それは考えるために、その権利範囲内であろうa[i]a[0]。誰かが構造ハックの整列の問題を回避しようとすると

typedef struct {
  uint32_tサイズ;
  uint8_t data [4]; // 4を使用して、パディングによる構造体のサイズの低下を回避
}

コンパイラーは賢くなり、配列サイズが実際には4であると想定します。

; 書かれたように
  foo = myStruct-> data [i];
; 解釈どおり(リトルエンディアンのハードウェアを想定)
  foo =((*(uint32_t *)myStruct-> data)>>(i << 3))&0xFF;

このような最適化は、特にmyStruct->dataと同じ操作でレジスタにロードできる場合は、妥当な場合がありますmyStruct->size。私はそのような最適化を禁止する標準では何も知りませんが、もちろん、4番目の要素を超えるものにアクセスすることを期待するコードを壊します。


1
可撓性アレイ部材は構造体ハッキングの正規版としてC99に加えた
MM

規格では、異なる配列メンバーへのアクセスは競合しないため、最適化が不可能になる傾向があります。
Ben Voigt 2015

@BenVoigt:C言語規格では、バイトの書き込みと含まれている単語の読み取りの効果を同時に指定していませんが、99.9%のプロセッサは書き込みが成功し、単語に新しいバージョンまたは古いバージョンの他のバイトの変更されていない内容とともにバイト。コンパイラがそのようなプロセッサをターゲットにしている場合、どのような競合が発生しますか?
スーパーキャット

@supercat:C言語標準では、2つの異なる配列要素への同時書き込みが競合しないことが保証されています。したがって、(書き込み中に読み取り)が機能するというあなたの主張は十分ではありません。
ベン・フォイト

@BenVoigt:コードの一部が配列要素0、1、2にあるシーケンスで書き込む場合、4つの要素すべてをlongに読み取り、3つを変更して、4つすべてを書き戻すことはできませんが、私は4つすべてをlongに読み取り、3つを変更し、下位16ビットをshortとして、ビット16〜23をバイトとして書き戻すことができると考えてください。あなたはそれに同意しませんか?そして、配列の要素を読み取るだけでよいコードは、単にそれらをlongに読み取って使用することが許可されます。
スーパーキャット2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.