数组声明
数组是一种类型,由连续分配的非空对象序列组成,这些对象具有特定的元素类型。这些对象的数量(数组大小)在数组生命周期内从不改变。
目录 |
[编辑] 语法
在数组声明的声明语法中,类型说明符序列指定元素类型(必须是完整的对象类型),而声明符的形式为
[ static (可选) 限定符 (可选) 表达式 (可选) ] 属性-说明符-序列 (可选) |
(1) | ||||||||
[ 限定符 (可选) static (可选) 表达式 (可选) ] 属性-说明符-序列 (可选) |
(2) | ||||||||
[ 限定符 (可选) * ] 属性-说明符-序列 (可选) |
(3) | ||||||||
表达式 | - | 除逗号运算符以外的任何表达式,指定数组中的元素数量 |
限定符 | - | const 、restrict 或 volatile 限定符的任意组合,仅允许在函数参数列表中使用;这限定了此数组参数转换成的指针类型 |
属性说明序列 | - | (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'
在函数参数列表中,数组声明符中允许出现额外的语法元素:关键字 在每次函数调用中,如果数组参数在 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) } 这通常与 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 } 如果大小是 变长数组及其派生类型(指向它们的指针等)通常被称为“可变修改类型”(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 起) |
[编辑] 限定符
如果数组类型使用 |
(直至 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
的操作数(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
多维数组可以在每个维度上进行可变修改(如果支持 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++ 文档,关于数组声明
|