名前空間
変種
操作

配列宣言

From cppreference.com
< c‎ | language

配列とは、特定の要素型を持つ、連続して割り当てられた非空のオブジェクトのシーケンスで構成される型です。これらのオブジェクトの数(配列サイズ)は、配列の寿命中は決して変更されません。

目次

[編集] 構文

配列宣言の宣言構文では、型指定子のシーケンスが要素型(完全なオブジェクト型でなければならない)を指定し、宣言子は次の形式をとります。

[ static(オプション) qualifiers (オプション) expression (オプション) ] attr-spec-seq (オプション) (1)
[ qualifiers (オプション) static(オプション) expression (オプション) ] attr-spec-seq (オプション) (2)
[ qualifiers (オプション) * ] attr-spec-seq (オプション) (3)
1,2) 一般的な配列宣言子の構文
3) サイズ未指定のVLAの宣言子(関数プロトタイプスコープでのみ出現可能) ここで
- カンマ演算子以外の任意の式は、配列の要素数を指定します。
qualifiers - constrestrict、または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'

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

[]の間にキーワードstaticを使用する配列パラメータを持つ関数への各関数呼び出しでは、実際には、指定された要素数以上の要素を持つ配列の最初の要素への有効なポインタである必要があります。

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

これは、restrict型修飾子とともに一般的に使用されます。

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
}

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

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

可変長配列とその型(それらへのポインタなど)は、一般に「可変修飾型」(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以降)

コンパイラがマクロ定数__STDC_NO_VLA__を整数定数1に定義する場合、VLAおよびVM型はサポートされません。

(C11 以降)
(C23まで)

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

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

[編集] 修飾子

配列型がconstvolatile、またはrestrict(since C99)修飾子で宣言された場合(typedefの使用によって可能)、配列型は修飾されませんが、その要素型は修飾されます。

(C23まで)

配列型とその要素型は、配列型が_Atomic修飾されることがないという例外を除いて、常に同一に修飾されていると見なされます。

(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

_Atomicは配列型に適用できませんが、アトミック型の配列は許可されます。

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)
  • 配列初期化に使用される文字列リテラルとして
  • _Alignof(since C11)(until C23)alignof(since C23)のオペランドとして
(C11 以降)

最初の要素へのポインタへの暗黙の変換を受けます。結果は左辺値ではありません。

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

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // prints size of array
printf("%zu\n", sizeof p); // prints size of a pointer

関数パラメータリストで配列型が使用される場合、対応するポインタ型に変換されます: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++ドキュメント配列宣言について)
English 日本語 中文(简体) 中文(繁體)