配列宣言
配列は特定の要素型のオブジェクトの連続的に確保された空でない並びから構成される型です。 オブジェクトの個数 (配列のサイズ) が配列の生存期間中に変わることはありません。
目次 |
[編集] 構文
配列宣言の宣言の構文では、型指定子の並びは要素型 (完全オブジェクト型でなければなりません) を指示し、宣言子は以下の形式を持ちます。
[ static (オプション) qualifiers(オプション) expression(オプション) ]
|
(1) | ||||||||
[ qualifiers(オプション) static (オプション) expression(オプション) ]
|
(2) | ||||||||
[ qualifiers(オプション) * ]
|
(3) | ||||||||
expression | - | コンマ演算子以外の任意の式。 配列内の要素数を指示します。 |
qualifiers | - | const、 restrict、 volatile 修飾子の任意の組み合わせ。 関数の仮引数リストでのみ使用できます。 この配列引数の変換後のポインタ型を修飾します。 |
float fa[11], *afp[17]; // fa は float 11個の配列です。 // afp は float へのポインタ17個の配列です。
[編集] 説明
配列型にはいくつかのバリエーション (サイズが定数かつ既知な配列、可変長配列、およびサイズが未知な配列) があります。
[編集] サイズが定数かつ既知な配列
配列宣言子の expression がゼロより大きな値を持つ整数定数式であり、要素型のサイズが既知 (つまり、要素が VLA でない) (C99以上)である場合は、その宣言子はサイズが定数かつ既知な配列を宣言します。
int n[10]; // 整数定数は定数式です。 char o[sizeof(double)]; // sizeof は定数式です。 enum { MAX_SZ=100 }; int n[MAX_SZ]; // enum 定数は定数式です。
サイズが定数かつ既知な配列は、その初期値を提供するために配列初期化子を使用できます。
int a[5] = {1,2,3}; // 1,2,3,0,0 に初期化される int[5] を宣言します。 char str[] = "abc"; // 'a','b','c','\0' に初期化される char[4] を宣言します。
関数の仮引数リストでは、配列初期化子で追加の構文要素、キーワード 配列型の仮引数の void fadd(double a[static 10], const double b[static 10]) { for (int i = 0; i < 10; i++) { if (a[i] < 0.0) return; a[i] += b[i]; } } // fadd の呼び出しはコンパイル時境界チェックを行います。 // また、10個の double をプリフェッチするみたいな最適化も許容されます。 int main(void) { double a[10] = {0}, b[20] = {0}; fadd(a, b); // OK。 double x[5] = {0}; fadd(x, b); // エラー、配列の実引数が小さすぎます。 } qualifiers が存在する場合は、それは配列型の変換先のポインタ型を修飾します。 int f(const int a[20]) { // この関数内では、 a は const int* (const int へのポインタ) 型です。 } int g(const int a[const 20]) { // この関数内では、 a は const int* const (const int への const ポインタ) 型です。 } これは restrict 型修飾子と共によく使用されます。 void fadd(double a[static restrict 10], const double b[static restrict 10]) { for (int i = 0; i < 10; i++) { // ループをアンロールしたりリオーダしたりできます。 if (a[i] < 0.0) break; a[i] += b[i]; } } 可変長配列 (VLA)expression が整数定数式でない場合、その宣言子はサイズが可変な配列のためのものです。 制御の流れがその宣言を通過するたびに、 expression が評価され (必ずゼロより大きな値に評価されなければなりません)、配列が確保されます (また、その宣言がスコープ外になるとき、 VLA の生存期間が終了します)。 各 VLA インスタンスのサイズは生存期間中は変わりませんが、同じコードを再び通過したとき、異なるサイズで確保されることはあります。 { int n = 1; label: int a[n]; // 毎回異なるサイズで計10回確保されます。 printf("The array has %zu elements\n", sizeof a / sizeof *a); if (n++ < 10) goto label; // VLA のスコープから抜けることにより、その生存期間が終了します。 } サイズが 可変長配列および可変長配列から派生した型 (可変長配列へのポインタなど) は一般に「可変修飾型」 (VM 型) と言います。 あらゆる可変修飾型のオブジェクトはブロックスコープまたは関数プロトタイプスコープでのみ宣言できます。 extern int n; int A[n]; // エラー、ファイルスコープの VLA。 extern int (*p2)[n]; // エラー、ファイルスコープの VM。 int B[100]; // OK、ファイルスコープのサイズが定数かつ既知な配列。 void fvla(int m, int C[m][m]); // OK、プロトタイプスコープの VLA。 VLA は自動記憶域期間でなければなりません。 VLA へのポインタ (VLA 自身ではなく) は静的記憶域期間でも構いません。 VM 型はリンケージを持つことはできません。 void fvla(int m, int C[m][m]) // OK、ブロックスコープ/自動期間の VLA へのポインタ。 { typedef int VLA[m][m]; // OK、ブロックスコープの VLA。 int D[m]; // OK、ブロックスコープ/自動期間の VLA。 // static int E[m]; // エラー、静的期間の VLA。 // extern int F[m]; // エラー、リンケージを持つ VLA。 int (*s)[m]; // OK、ブロックスコープ/自動期間の VM。 // extern int (*r)[m]; // エラー、リンケージを持つ VM。 static int (*q)[m] = &B; // OK、ブロックスコープ/静的期間の VM。 } 可変修飾型は構造体や共用体のメンバにできません。 struct tag { int z[n]; // エラー、 VLA の構造体メンバ。 int (*y)[n]; // エラー、 VM の構造体メンバ。 }; |
(C99以上) |
コンパイラがマクロ定数 __STDC_NO_VLA__ を整数定数 1 に定義している場合は、 VLA および VM 型はサポートされません。 |
(C11以上) |
[編集] サイズが未知な配列
配列宣言子の expression が省略された場合は、サイズが未知な配列を宣言します。 関数の仮引数リスト内の場合 (その場合は、そのような配列はポインタに変換されます) および初期化子が提供されている場合を除いて、そのような型は不完全型になります (ちなみに、サイズに *
を用いて宣言された、サイズが未指定な VLA は、完全型です) (C99以上)。
extern int x[]; // x の型は「境界が未知な int の配列」です。 int a[] = {1,2,3}; // a の型は「int 3個の配列」です。
構造体定義の中で、最後のメンバとして、サイズが未知な配列が現れても構いません (名前付きのメンバが他に少なくとも1個は存在しなければなりません)。 これはフレキシブル配列メンバと呼ばれる特別なケースです。 詳細については構造体を参照してください。 struct s { int n; double d[]; }; // s.d はフレキシブル配列メンバです。 struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // d が double d[8] であったかのように動作します。 |
(C99以上) |
[編集] 修飾子
配列型が const、 volatile、 restrict (C99以上)、または _Atomic (C11以上) 修飾子付きで宣言された場合 (これは typedef を用いることによって可能になります)、配列型ではなく、その要素型が修飾されます。
typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // const int の配列の配列。 int* pi = a[0]; // エラー、 a[0] は const int* 型です。
[編集] 代入
配列型のオブジェクトは変更可能な左辺値ではありません。 アドレスを取ることはできても、代入演算子の左側に現れることはできません。 しかし、配列メンバを持つ構造体は変更可能な左辺値であり、代入できます。
int a[3] = {1,2,3}, b[3] = {4,5,6}; int (*p)[3] = &a; // OK、配列のアドレスは取れます。 // a = b; // エラー、配列の代入はできません。 struct { int c[3]; } s1, s2 = {3,4,5}; s1 = s2; // OK、配列メンバを持つ構造体は代入できます。
[編集] 配列からポインタへの変換
配列型の左辺値式は、
|
(C11以上) |
以外のあらゆる文脈において、使用されたとき、その最初の要素を指すポインタに暗黙に変換されます。 その結果は左辺値ではありません。
配列が register 宣言された場合、そのような変換を試みるプログラムの動作は未定義です。
配列型が関数の仮引数リストで使用されたときは、対応するポインタ型に変換されます。 int f(int a[2]) と int f(int* a) は同じ関数を宣言します。 関数の実引数の型はポインタ型であるため、配列の実引数を用いる関数呼び出しは配列からポインタへの変換を行います。 実引数の配列のサイズは、呼ばれた関数の側では利用可能ではなく、明示的に渡さなければなりません。
void f(int a[], int sz) // 実際には void f(int* a, int sz) を宣言します。 { for(int i = 0; i < sz; ++i) printf("%d\n", a[i]); } int main(void) { int a[10]; f(a, 10); // a は int* に変換され、そのポインタが渡されます。 }
[編集] 多次元配列
配列の要素型が別の配列であるとき、その配列は多次元であると言います。
// int 3個の配列2個の配列 int a[2][3] = {{1,2,3}, // 行優先レイアウトの {4,5,6}}; // 2×3の行列と考えることもできます。
配列からポインタへの変換が適用されるとき、多次元配列はその最初の要素 (すなわち1行目) を指すポインタに変換されることに注意してください。
int a[2][3]; // 2×3の行列。 int (*p1)[3] = a; // 1行目の要素3個へのポインタ。 int b[3][3][3]; // 3×3×3の立方体。 int (*p2)[3][3] = b; // 1平面目の要素3×3個へのポインタ。
多次元配列はいずれの次元も可変修飾にできます。 int n = 10; int a[n][2*n]; |
(C99以上) |
[編集] ノート
長さゼロの配列の宣言は許されていません。 しかし一部のコンパイラは拡張として (一般的にはC99より前のフレキシブル配列メンバの実装として) そのようなものを提供しています。
VLA のサイズの expression が副作用を持つ場合、その副作用は生成されることが保証されます。 ただし、 expression が sizeof 式の一部であって、その sizeof 式の結果が expression に依存しない場合は除きます。
int n = 5; int m = 7; size_t sz = sizeof(int (*)[n++]); // n はインクリメントされるかもしれないしされないかもしれません。