Namespaces
Variants

Array declaration

From cppreference.net

配列は、特定の element type を持つ連続的に割り当てられた空でないオブジェクトシーケンスからなる型です。それらのオブジェクトの数(配列サイズ)は配列の生存期間中決して変化しません。

目次

構文

配列宣言の 宣言文法 において、 type-specifier シーケンスは element type (完全オブジェクト型でなければならない)を指定し、 declarator は以下の形式をとります:

[ static (オプション) 修飾子  (オプション)  (オプション) ] 属性指定子列  (オプション) (1)
[ 修飾子  (オプション) static (オプション)  (オプション) ] 属性指定子列  (オプション) (2)
[ 修飾子  (オプション) * ] 属性指定子列  (オプション) (3)
1,2) 一般的な配列宣言子の構文
3) 未指定サイズのVLAの宣言子(関数プロトタイプスコープでのみ使用可能) ここで
expression - カンマ演算子以外の任意の式、 comma operator 以外の任意の式で、配列内の要素数を指定する
qualifiers - const restrict 、または volatile 修飾子の任意の組み合わせ。関数パラメータリスト内でのみ許可され、この配列パラメータが変換されるポインタ型を修飾する
attr-spec-seq - (C23) オプションの attributes リストで、宣言された配列に適用される
float fa[11], *afp[17]; // fa は11個のfloatの配列
                        // afp は17個のfloatへのポインタの配列

説明

配列型にはいくつかのバリエーションがあります:既知の定数サイズの配列、可変長配列、および未知のサイズの配列。

定数既知サイズの配列

もし配列宣言子の expression が、ゼロより大きい値を持つ 整数定数式 であり かつ要素型が既知の定数サイズを持つ型である場合(つまり要素がVLAではない) (C99以降) 、その宣言子は定数既知サイズの配列を宣言します:

int n[10]; // 整数定数は定数式です
char o[sizeof(double)]; // sizeofは定数式です
enum { MAX_SZ=100 };
int n[MAX_SZ]; // enum定数は定数式です

既知の定数サイズの配列は、 array initializers を使用して初期値を提供できます:

int a[5] = {1,2,3}; // int[5]を宣言し、1,2,3,0,0で初期化
char str[] = "abc"; // char[4]を宣言し、'a','b','c','\0'で初期化

関数のパラメータリストでは、配列宣言子内で追加の構文要素が許可されています: static キーワードと qualifiers で、これらはサイズ式の前に任意の順序で現れることができます(サイズ式が省略されている場合でも現れることがあります)。

関数呼び出し において、配列パラメータが static キーワードを [ ] の間に使用する関数への呼び出しでは、実引数の値は expression で指定される要素数以上の配列の先頭要素を指す有効なポインタでなければなりません:

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

可変長配列

expression 整数定数式 でない場合、その宣言子は可変長配列を宣言します。

制御フローが宣言を通過するたびに、 expression が評価され(常にゼロより大きい値を評価する必要があります)、配列が割り当てられます(対応して、 lifetime は宣言がスコープ外に出ると終了します)。各VLAインスタンスのサイズはその生存期間中変更されませんが、同じコードを再度通過する際には、異なるサイズで割り当てられる可能性があります。

#include <stdio.h>
int main(void)
{
   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のスコープを離れるとその寿命が終了する
}

サイズが * の場合、未指定サイズのVLAの宣言です。このような宣言は関数プロトタイプスコープでのみ現れ、完全型の配列を宣言します。実際、関数プロトタイプスコープ内のすべてのVLA宣言子は、 expression * で置き換えられたかのように扱われます。

void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{
    printf("%zu\n", sizeof a); // sizeof(int*) と同じ
}

可変長配列およびそれから派生する型(それらへのポインタなど)は一般に「可変修飾型」(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
    s = malloc(m * sizeof(int)); // OK: sは割り当てられた記憶域のVLAを指す
//  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以降)
(C23まで)

コンパイラがマクロ定数 __STDC_NO_VLA__ を整数定数 1 に定義する場合、自動記憶域期間を持つVLAオブジェクトはサポートされません。

割り当てられた記憶域期間を持つVM型とVLAのサポートは必須です。

(C23以降)

未知サイズの配列

配列宣言子の expression が省略された場合、未知のサイズの配列を宣言します。関数パラメータリスト(そのような配列はポインタに変換される)および initializer が利用可能な場合を除き、そのような型は incomplete type です (未指定サイズのVLAは、 * をサイズとして宣言されると完全型であることに注意) (C99以降) :

extern int x[]; // xの型は「要素数不明のint配列」
int a[] = {1,2,3}; // aの型は「3要素のint配列」

struct 定義内では、未知のサイズの配列が最後のメンバーとして現れることがあります(少なくとも1つ以上の名前付きメンバーが存在する限り)。この場合、これは フレキシブル配列メンバー として知られる特別なケースです。詳細は struct を参照してください:

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以降)

修飾子

配列型が const volatile 、または restrict (C99以降) 修飾子で宣言された場合( typedef を使用することで可能)、配列型自体は修飾されませんが、その要素型は修飾されます:

(C23まで)

配列型とその要素型は常に同一の修飾を持つと見なされます。ただし、配列型が _Atomic 修飾を持つと見なされることはありません。

(C23以降)
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // const intの配列の配列
int* pi = a[0]; // エラー: a[0]はconst int*型
void* unqual_ptr = a; // C23まではOK; C23以降はエラー
// 注記: clangはC89-C17モードでもC++/C23のルールを適用する

_Atomic は配列型に適用することはできないが、アトミック型の配列は許可される。

typedef int A[2];
// _Atomic A a0 = {0};    // エラー
// _Atomic(A) a1 = {0};   // エラー
_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; // OK、aのアドレスは取得可能
// a = b;            // エラー、aは配列
struct { int c[3]; } s1, s2 = {3,4,5};
s1 = s2; // OK: 配列メンバーを持つ構造体は代入可能

配列からポインタへの変換

配列型の lvalue式 は、以下のいずれかの文脈以外で使用された場合

(C11以降)

その配列は、その最初の要素へのポインタへ 暗黙変換 されます。結果は左辺値ではありません。

配列が register として宣言された場合、そのような変換を試みるプログラムの動作は未定義です。

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // 配列のサイズを出力
printf("%zu\n", sizeof p); // ポインタのサイズを出力

配列型が関数のパラメータリストで使用される場合、対応するポインタ型に変換されます: int f ( int a [ 2 ] ) int f ( int * a ) は同じ関数を宣言します。関数の実際のパラメータ型がポインタ型であるため、配列引数を用いた関数呼び出しは配列からポインタへの変換を実行します。引数配列のサイズは呼び出された関数では利用できず、明示的に渡す必要があります:

#include <stdio.h>
void f(int a[], int sz) // 実際には void f(int* a, int sz) を宣言
{
    for (int i = 0; i < sz; ++i)
        printf("%d\n", a[i]);
}
void g(int (*a)[10]) // 配列へのポインタ引数は変換されない
{
    for (int i = 0; i < 10; ++i)
        printf("%d\n", (*a)[i]);
}
int main(void)
{
    int a[10] = {0};
    f(a, 10); // a を int* に変換し、ポインタを渡す
    g(&a);    // 配列へのポインタを渡す(サイズを渡す必要がない)
}

多次元配列

配列の要素型が別の配列である場合、その配列は多次元であると言います:

// 2つの配列の配列、各々3つのintを持つ
int a[2][3] = {{1,2,3},  // 2x3行列として見なせる
               {4,5,6}}; // 行優先レイアウトで

配列からポインタへの変換が適用される場合、多次元配列はその最初の要素へのポインタに変換されることに注意してください。例:最初の行へのポインタ:

int a[2][3]; // 2x3 行列
int (*p1)[3] = a; // 最初の3要素行へのポインタ
int b[3][3][3]; // 3x3x3 立方体
int (*p2)[3][3] = b; // 最初の3x3平面へのポインタ

多次元配列は、すべての次元で可変修飾可能です (VLAがサポートされている場合) (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はインクリメントされるが、mはインクリメントされる場合とされない場合がある

参考文献

  • 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++ ドキュメント for 配列宣言