配列宣言
配列とは、特定の要素型を持つ、連続して割り当てられた非空のオブジェクトのシーケンスで構成される型です。これらのオブジェクトの数(配列サイズ)は、配列の寿命中は決して変更されません。
目次 |
[編集] 構文
配列宣言の宣言構文では、型指定子のシーケンスが要素型(完全なオブジェクト型でなければならない)を指定し、宣言子は次の形式をとります。
[ static(オプション) qualifiers (オプション) expression (オプション) ] attr-spec-seq (オプション) |
(1) | ||||||||
[ qualifiers (オプション) static(オプション) expression (オプション) ] attr-spec-seq (オプション) |
(2) | ||||||||
[ qualifiers (オプション) * ] attr-spec-seq (オプション) |
(3) | ||||||||
| 式 | - | カンマ演算子以外の任意の式は、配列の要素数を指定します。 |
| qualifiers | - | const、restrict、またはvolatile修飾子の任意の組み合わせ。関数パラメータリストでのみ許可されます。これは、この配列パラメータが変換されるポインタ型を修飾します。 |
| attr-spec-seq | - | (C23)宣言された配列に適用される属性のオプションリスト。 |
float fa[11], *afp[17]; // fa is an array of 11 floats // afp is an array of 17 pointers to floats
[編集] 説明
配列型にはいくつかのバリエーションがあります。固定既知サイズの配列、可変長配列、およびサイズ不明の配列です。
[編集] 固定既知サイズの配列
配列宣言子のexpressionが、値がゼロより大きい整数定数式である場合かつ要素型が定数サイズが既知の型である場合(つまり、要素がVLAでない場合)(since C99)、その宣言子は固定既知サイズの配列を宣言します。
int n[10]; // integer constants are constant expressions char o[sizeof(double)]; // sizeof is a constant expression enum { MAX_SZ=100 }; int n[MAX_SZ]; // enum constants are constant expressions
固定既知サイズの配列は、配列初期化子を使用して初期値を提供できます。
int a[5] = {1,2,3}; // declares int[5] initialized to 1,2,3,0,0 char str[] = "abc"; // declares char[4] initialized to 'a','b','c','\0'
|
関数パラメータリストでは、配列宣言子の内部に追加の構文要素が許可されます。キーワード
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]; } } // a call to fadd may perform compile-time bounds checking // and also permits optimizations such as prefetching 10 doubles int main(void) { double a[10] = {0}, b[20] = {0}; fadd(a, b); // OK double x[5] = {0}; fadd(x, b); // undefined behavior: array argument is too small } qualifiersが存在する場合、それらは配列パラメータ型が変換されるポインタ型を修飾します。 int f(const int a[20]) { // in this function, a has type const int* (pointer to const int) } int g(const int a[const 20]) { // in this function, a has type const int* const (const pointer to const int) } これは、 void fadd(double a[static restrict 10], const double b[static restrict 10]) { for (int i = 0; i < 10; i++) // loop can be unrolled and reordered { if (a[i] < 0.0) break; a[i] += b[i]; } } 可変長配列expressionが整数定数式でない場合、宣言子は可変長配列用です。 制御フローが宣言を通過するたびに、expressionが評価され(常に1より大きい値に評価される必要があります)、配列が割り当てられます(対応して、VLAの寿命は宣言がスコープを外れると終了します)。各VLAインスタンスのサイズは、その寿命中は変化しませんが、同じコードを再度通過すると、異なるサイズで割り当てられる場合があります。 このコードを実行 #include <stdio.h> int main(void) { int n = 1; label:; int a[n]; // re-allocated 10 times, each with a different size printf("The array has %zu elements\n", sizeof a / sizeof *a); if (n++ < 10) goto label; // leaving the scope of a VLA ends its lifetime } サイズが 可変長配列とその型(それらへのポインタなど)は、一般に「可変修飾型」(VM)として知られています。任意の可変修飾型のオブジェクトは、ブロックスコープまたは関数プロトタイプスコープでのみ宣言できます。 extern int n; int A[n]; // Error: file scope VLA extern int (*p2)[n]; // Error: file scope VM int B[100]; // OK: file-scope array of constant known size void fvla(int m, int C[m][m]); // OK: prototype-scope VLA VLAは、自動または割り当てられた記憶域期間を持つ必要があります。VLAへのポインタは、VLA自体ではありませんが、静的記憶域期間を持つこともできます。VM型はリンクを持てません。 void fvla(int m, int C[m][m]) // OK: block scope/auto duration pointer to VLA { typedef int VLA[m][m]; // OK: block scope VLA int D[m]; // OK: block scope/auto duration VLA // static int E[m]; // Error: static duration VLA // extern int F[m]; // Error: VLA with linkage int (*s)[m]; // OK: block scope/auto duration VM s = malloc(m * sizeof(int)); // OK: s points to VLA in allocated storage // extern int (*r)[m]; // Error: VM with linkage static int (*q)[m] = &B; // OK: block scope/static duration VM} } 可変修飾型は、構造体または共用体のメンバーにはできません。 struct tag { int z[n]; // Error: VLA struct member int (*y)[n]; // Error: VM struct member }; |
(C99以降) |
|
コンパイラがマクロ定数 |
(C11 以降) (C23まで) |
|
コンパイラがマクロ定数 VM型および割り当てられた記憶域期間を持つVLAのサポートは義務付けられています。 |
(C23以降) |
[編集] サイズ不明の配列
配列宣言子のexpressionが省略されている場合、それはサイズ不明の配列を宣言します。関数パラメータリスト(そのような配列はポインタに変換される)および初期化子が利用可能な場合を除き、そのような型は不完全型です(サイズとして*で宣言されたサイズ未指定のVLAは完全型であることに注意してください)(since C99)。
extern int x[]; // the type of x is "array of unknown bound of int" int a[] = {1,2,3}; // the type of a is "array of 3 int"
|
構造体定義内では、サイズ不明の配列が最後のメンバーとして(少なくとも他の名前付きメンバーが1つある限り)出現する可能性があり、その場合、それはフレキシブル配列メンバーとして知られる特別なケースです。詳細は構造体を参照してください。 struct s { int n; double d[]; }; // s.d is a flexible array member struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]
|
(C99以降) |
[編集] 修飾子
|
配列型が |
(C23まで) |
|
配列型とその要素型は、配列型が |
(C23以降) |
typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // array of array of const int int* pi = a[0]; // Error: a[0] has type const int* void* unqual_ptr = a; // OK until C23; error since C23 // Notes: clang applies the rule in C++/C23 even in C89-C17 modes
|
typedef int A[2]; // _Atomic A a0 = {0}; // Error // _Atomic(A) a1 = {0}; // Error _Atomic int a2[2] = {0}; // OK _Atomic(int) a3[2] = {0}; // OK |
(C11 以降) |
[編集] 代入
配列型のオブジェクトは変更可能な左辺値ではなく、そのアドレスを取得することはできますが、代入演算子の左辺に現れることはできません。ただし、配列メンバーを持つ構造体は変更可能な左辺値であり、代入できます。
int a[3] = {1,2,3}, b[3] = {4,5,6}; int (*p)[3] = &a; // okay, address of a can be taken // a = b; // error, a is an array struct { int c[3]; } s1, s2 = {3,4,5}; s1 = s2; // okay: can assign structs holding array members
[編集] 配列からポインタへの変換
配列型の任意の左辺値式は、次のいずれかのコンテキスト以外で使用される場合:
- アドレス演算子のオペランドとして
sizeofのオペランドとしてtypeofおよびtypeof_unqualのオペランドとして(since C23)- 配列初期化に使用される文字列リテラルとして
|
(C11 以降) |
最初の要素へのポインタへの暗黙の変換を受けます。結果は左辺値ではありません。
配列がregisterとして宣言されていた場合、そのような変換を試みるプログラムの動作は未定義です。
関数パラメータリストで配列型が使用される場合、対応するポインタ型に変換されます:int f(int a[2])およびint f(int* a)は同じ関数を宣言します。関数の実際にはパラメータ型はポインタ型であるため、配列引数を持つ関数呼び出しは配列からポインタへの変換を実行します。引数配列のサイズは呼び出される関数からは利用できず、明示的に渡す必要があります。
#include <stdio.h> void f(int a[], int sz) // actually declares void f(int* a, int sz) { for (int i = 0; i < sz; ++i) printf("%d\n", a[i]); } void g(int (*a)[10]) // pointer to array parameter is not transformed { for (int i = 0; i < 10; ++i) printf("%d\n", (*a)[i]); } int main(void) { int a[10] = {0}; f(a, 10); // converts a to int*, passes the pointer g(&a); // passes a pointer to the array (no need to pass the size) }
[編集] 多次元配列
配列の要素型が別の配列である場合、その配列は多次元であると言われます。
// array of 2 arrays of 3 ints each int a[2][3] = {{1,2,3}, // can be viewed as a 2x3 matrix {4,5,6}}; // with row-major layout
配列からポインタへの変換が適用される場合、多次元配列は最初の要素へのポインタに変換されることに注意してください。例:最初の行へのポインタ。
int a[2][3]; // 2x3 matrix int (*p1)[3] = a; // pointer to the first 3-element row int b[3][3][3]; // 3x3x3 cube int (*p2)[3][3] = b; // pointer to the first 3x3 plane
|
多次元配列は、すべての次元で可変修飾される可能性がありますVLAsがサポートされている場合(since C11)。 int n = 10; int a[n][2*n]; |
(C99以降) |
[編集] 注
ゼロ長配列宣言は許可されていません。一部のコンパイラは拡張機能として提供していますが(通常はフレキシブル配列メンバーのC99より前の実装)、標準ではありません。
VLAのサイズexpressionに副作用がある場合、sizeof式の一部であり、その結果がそれに依存しない場合を除き、副作用が発生することが保証されています。
int n = 5, m = 5; size_t sz = sizeof(int (*[n++])[m++]); // n is incremented, m may or may not be incremented
[編集] 参照
- C23標準 (ISO/IEC 9899:2024)
- 6.7.6.2 配列宣言子(p:TBD)
- C17標準 (ISO/IEC 9899:2018)
- 6.7.6.2 配列宣言子(p:94-96)
- C11標準 (ISO/IEC 9899:2011)
- 6.7.6.2 配列宣言子(p:130-132)
- C99標準 (ISO/IEC 9899:1999)
- 6.7.5.2 配列宣言子(p:116-118)
- C89/C90標準 (ISO/IEC 9899:1990)
- 3.5.4.2 配列宣言子
[編集] 関連項目
| C++ドキュメント (配列宣言について)
|