数组参数
数组作为函数参数时,会发生"数组退化"——数组名转换为指向首元素的指针。这意味着函数内部无法通过 sizeof 获取数组大小,必须显式传递数组长度。理解这一机制,是正确编写处理数组的函数的关键。
数组退化
当数组名作为函数参数传递时,它退化为指针:
void print_array(int arr[], int n)
{
printf("sizeof(arr) = %zu\n", sizeof(arr)); /* sizeof(int*),不是数组大小 */
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main(void)
{
int data[] = {1, 2, 3, 4, 5};
printf("sizeof(data) = %zu\n", sizeof(data)); /* 20(5 * 4) */
print_array(data, 5);
return 0;
}
void print_array(int arr[], int n) 中的 arr 实际上等价于 int *arr。方括号 [] 只是语法糖,提醒调用者应该传入数组。
以下三种声明完全等价:
void func(int arr[]);
void func(int arr[10]); /* 10 被忽略 */
void func(int *arr); /* 最本质的形式 */
传递数组大小
由于数组退化,函数内部不知道数组大小,必须显式传递:
/* 正确:传递数组和大小 */
double average(const int arr[], int n)
{
if (n <= 0) return 0.0;
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
return (double)sum / n;
}
int data[] = {1, 2, 3, 4, 5};
double avg = average(data, 5);
多维数组参数
多维数组作为参数时,除第一维外,其他维度必须显式指定:
/* 二维数组 */
void print_matrix(int mat[][3], int rows)
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++)
printf("%d ", mat[i][j]);
printf("\n");
}
}
/* 等价声明:指针形式 */
void print_matrix(int (*mat)[3], int rows)
int main(void)
{
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
print_matrix(matrix, 2);
return 0;
}
int mat[][3] 等价于 int (*mat)[3]:指向包含 3 个 int 的数组的指针。
为什么不能省略第二维?因为编译器需要知道每行的大小来计算 mat[i][j] 的地址:
mat[i][j] 的地址 = mat + i * (3 * sizeof(int)) + j * sizeof(int)
C99 变长数组参数
C99 允许变长数组(VLA)作为参数,维度可以用前面的参数指定:
/* VLA 参数 */
void print_matrix(int rows, int cols, int mat[rows][cols])
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++)
printf("%d ", mat[i][j]);
printf("\n");
}
}
int main(void)
{
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
print_matrix(2, 3, matrix);
return 0;
}
注意:VLA 参数中,维度参数必须出现在数组参数之前。
static 修饰数组参数
C99 允许用 static 修饰数组参数,提示编译器数组至少有指定数量的元素:
void process(int arr[static 10]) /* 提示:arr 至少指向 10 个元素 */
{
/* 编译器可以假设 arr[0] 到 arr[9] 有效 */
}
这只是优化提示,不是运行时检查。编译器可以利用这个信息进行优化,但不会验证实际传入的数组大小。
常见错误
忘记传递大小:
void print(int arr[])
{
int n = sizeof(arr) / sizeof(arr[0]); /* 错误:sizeof(arr) = sizeof(int*) */
/* ... */
}
二维数组维度不匹配:
void func(int arr[][3]); /* 第二维是 3 */
int matrix[2][4];
func(matrix); /* 错误:第二维不匹配 */
数组与指针混淆:
void func(int *arr);
int data[] = {1, 2, 3};
func(data); /* 合法:数组退化为指针 */
int x = 10;
func(&x); /* 合法但危险:传递单个 int 的地址 */
/* 函数可能越界访问 */
最佳实践
- 始终显式传递数组大小
- 用
const修饰不修改的数组参数 - 二维数组参数中,除第一维外都显式指定
- 考虑用 VLA(C99)提高灵活性
- 大型数组考虑传递指针而非复制
- 文档化函数对数组的期望(大小、是否修改)