web-dev-qa-db-ja.com

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

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

サンプルプログラム

#include <stdio.h>

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

明確化

Darhazerのコメントの可変長のように指摘される代わりに、ゼロ長配列がこのように初期化されたかどうかを実際に把握しようとしています。

これは、一部のコードを公開する必要があるため、SIZE0として定義されているケースを処理する必要があるかどうかを把握しようとしているためです。静的に定義されたint array[SIZE];

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

私は主にエラーについて疑問に思っていたので、私はルンディンの答えを正しいとタグ付けしています(ナワズの答えは最初でしたが、完全ではありませんでした)-他の人はテールパッド構造の実際の使用を指摘していましたが、まさに私が探していたもの。

119
Alex Koay

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

ISO 9899:2011 6.7.6.2:

式が定数式である場合、ゼロより大きい値を持たなければなりません。

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

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

79
Lundin

通常、許可されていません。

ただし、柔軟な配列を使用することはCの現在の慣行です。

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

デモンストレーション:

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 } };

これはtail-padded structures(この用語はC99標準の発行より前)またはstruct hackとも呼ばれます(指摘してくれたJoe Wreschnigに感謝)。

ただし、この構文はC99で最近標準化された(および効果が保証された)だけです。一定のサイズが必要になる前。

  • 1は可搬性のある方法でしたが、かなり奇妙でした
  • 0は意図を示すのに優れていましたが、規格に関する限り、合法ではなく、一部のコンパイラ(gccを含む)による拡張としてサポートされていました

ただし、テールパディングのプラクティスは、ストレージが利用可能であるという事実(慎重なmalloc)に依存しているため、一般的なスタックの使用法では不適です。

74
Matthieu M.

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

GCCを使用している場合は、-pedanticオプションを使用してコンパイルします。それはwarningを与え、言います:

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

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

57
Nawaz

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

char someCondition[ condition ];

conditionがfalseの場合、コンパイル時エラーが発生します。ただし、コンパイラはこれを許可しているため、次のものを使用します。

char someCondition[ 2 * condition - 1 ];

これにより、サイズが1または-1になります。サイズ-1を受け入れるコンパイラは見つかりませんでした。

24
James Kanze

この引数に関するgccのオンラインドキュメントの ページ全体 があることを追加します。

いくつかの引用:

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

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

そして

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

だからできる

int arr[0] = { 1 };

とブーム:-)

8
xanatos

ゼロ長配列のもう1つの用途は、可変長オブジェクト(C99以前)の作成です。 ゼロ長配列は、different柔軟な配列0なしの[]があります。

gcc doc から引用:

GNU Cでは長さゼロの配列を使用できます。これらは、実際に可変長オブジェクトのヘッダーである構造の最後の要素として非常に便利です。

 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演算子は適用されない場合があります。

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

7
Duke

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

サイズ1の配列を使用して一般的に実装されるstruct hackは危険であり、コンパイラーがそれを破らないようにする必要はないと思います。たとえば、コンパイラがint a[1]を検出した場合、a[i]a[0]と見なすことはその権利の範囲内にあることを期待します。誰かが構造ハックのアライメントの問題を次のように回避しようとすると

 typedef struct {
 uint32_t size; 
 uint8_t data [4]; //構造体のサイズからパディングがスローされるのを避けるために、4つを使用します
} 

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

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

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

6
supercat