名稱空間
變體
操作

陣列宣告

來自 cppreference.com
< c‎ | 語言

陣列是一種型別,由連續分配的非空物件序列組成,這些物件具有特定的元素型別。這些物件的數量(陣列大小)在陣列生命週期內從不改變。

目錄

[編輯] 語法

在陣列宣告的宣告語法中,型別說明符序列指定元素型別(必須是完整的物件型別),而宣告符的形式為

[ static(可選) 限定符 (可選) 表示式 (可選) ] 屬性-說明符-序列 (可選) (1)
[ 限定符 (可選) static(可選) 表示式 (可選) ] 屬性-說明符-序列 (可選) (2)
[ 限定符 (可選) * ] 屬性-說明符-序列 (可選) (3)
1,2) 通用陣列宣告符語法
3) 未指定大小的 VLA 宣告符(只能出現在函式原型作用域中),其中
表示式 - 逗號運算子以外的任何表示式,指定陣列中的元素數量
限定符 - constrestrictvolatile 限定符的任意組合,僅允許在函式引數列表中使用;這限定了此陣列引數轉換成的指標型別
屬性說明序列 - (C23 起)可選的屬性列表,應用於宣告的陣列
float fa[11], *afp[17]; // fa is an array of 11 floats
                        // afp is an array of 17 pointers to floats

[編輯] 解釋

陣列型別有幾種變體:已知固定大小的陣列、變長陣列和未知大小的陣列。

[編輯] 已知固定大小的陣列

如果陣列宣告符中的表示式是大於零的整型常量表達式且元素型別是已知固定大小的型別(即元素不是 VLA)(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'

在函式引數列表中,陣列宣告符中允許出現額外的語法元素:關鍵字static限定符,它們可以以任何順序出現在大小表示式之前(即使省略大小表示式也可以出現)。

在每次函式呼叫中,如果陣列引數在[]之間使用關鍵字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];
    }
}
// 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
}

如果存在限定符,它們將限定陣列引數型別轉換為的指標型別。

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(並且它必須始終計算為大於零的值),並分配陣列(相應地,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 起)

[編輯] 未知大小的陣列

如果陣列宣告符中的表示式被省略,則它宣告一個未知大小的陣列。除了在函式引數列表(其中此類陣列轉換為指標)和存在初始化器時,此類型別是不完整型別(請注意,使用*作為大小宣告的未指定大小的 VLA 是完整型別)(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"

結構體定義中,未知大小的陣列可以作為最後一個成員出現(只要至少有一個其他命名成員),在這種情況下,它是一個稱為柔性陣列成員的特殊情況。有關詳細資訊,請參閱結構體

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

[編輯] 限定符

如果陣列型別使用constvolatilerestrict(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

[編輯] 陣列到指標的轉換

任何陣列型別的左值表示式,當用於以下情況以外的任何上下文時

(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

多維陣列可以在每個維度上進行可變修改(如果支援 VLA)(C11 起)

int n = 10;
int a[n][2*n];
(C99 起)

[編輯] 注意

不允許零長度陣列宣告,儘管有些編譯器將其作為擴充套件提供(通常作為柔性陣列成員的 C99 之前的實現)。

如果 VLA 的大小表示式具有副作用,則除非它是 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: 待定)
  • 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++ 文件,關於陣列宣告