指针和数组是 C 语言中最容易混淆的两个概念。很多初学者认为数组名就是指针,但实际上它们有本质区别。本文从内存模型、类型系统和编译器行为三个角度,彻底讲清指针与数组的关系。
一、数组名的退化规则
在大多数表达式中,数组名会退化为指向首元素的指针。但以下三种情况例外:
sizeof(arr)— 返回整个数组的字节数&arr— 返回整个数组的地址,类型为int (*)[N]- 字符串初始化 —
char s[] = "abc";中的s是数组,不是指针
/* 数组名退化演示 */ int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; /* arr 退化为 &arr[0] */ printf("%zu\n", sizeof(arr)); /* 20 (5 * 4) */ printf("%zu\n", sizeof(p)); /* 8 或 4 (指针大小) */
二、&arr 与 arr 的本质区别
虽然 arr 和 &arr 的数值相同,但类型完全不同,导致指针运算结果差异巨大:
int arr[5] = {10, 20, 30, 40, 50}; /* arr 退化为 int*,+1 跳过一个 int */ printf("%d\n", *(arr + 1)); /* 20 */ /* &arr 是 int (*)[5],+1 跳过整个数组 */ printf("%p\n", (void*)(arr + 1)); /* +4 字节 */ printf("%p\n", (void*)(&arr + 1)); /* +20 字节 */
关键结论:
arr 的类型是 int*,&arr 的类型是 int (*)[5]。前者 +1 移动 4 字节,后者 +1 移动 20 字节。三、函数参数中的"数组"
函数参数中的数组声明会被编译器自动调整为指针。以下三种写法完全等价:
/* 三种写法完全等价 */ void foo(int arr[10]); void foo(int arr[]); void foo(int *arr); /* 因此无法通过 sizeof 获取数组长度 */ void print_arr(int arr[10]) { printf("%zu\n", sizeof(arr)); /* 8 (指针大小!) */ }
四、二维数组与指针
二维数组名退化为指向数组的指针,而非二级指针:
int mat[3][4]; int (*p)[4] = mat; /* 正确:p 是指向 int[4] 的指针 */ /* int **pp = mat; */ /* 错误:类型不匹配 */ /* 访问元素 */ mat[1][2] = 100; p[1][2] = 100; *(*(p + 1) + 2) = 100; /* 三者等价 */
五、总结对照表
int arr[5]— 分配 20 字节连续内存,arr 不可赋值int *p = arr— p 存储 arr 的首地址,p 可重新赋值sizeof(arr)— 数组总大小(20)sizeof(p)— 指针大小(4 或 8)&arr— 整个数组的地址,类型int (*)[5]arr + 1— 跳过 4 字节(一个 int)&arr + 1— 跳过 20 字节(整个数组)
记忆口诀:数组名不是指针,只是在表达式中"假装"成指针。sizeof 和 & 能看穿这个伪装。