指针与数组的恩怨:数组名真的是指针吗?&arr 和 arr 的区别
说实话,这个问题我面试过不少人,十有八九都会栽在这里。
很多人张口就来:「数组名就是指针啊,不就是指向第一个元素的地址吗?」
嗯,这话说对了一半,但另一半——恰恰是面试官想听的——全错了。
我自己刚入行那会儿也犯过这个错。有一次调试一个通信协议栈,数组传参传得飞起,结果某个模块死活拿不到正确的数组长度。查了两天,最后发现是 sizeof(arr) 在函数内部返回了 4,而不是我期望的 1024。那一刻我才真正意识到:数组名和指针,根本不是一回事。
先看一个最经典的例子
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("arr = %p\n", arr); // 输出:0x7ffc12345670
printf("&arr = %p\n", &arr); // 输出:0x7ffc12345670
printf("arr + 1 = %p\n", arr + 1); // 输出:0x7ffc12345674
printf("&arr + 1 = %p\n", &arr + 1); // 输出:0x7ffc12345684
return 0;
}
看到了吗?arr 和 &arr 打印出来的地址值一模一样。但加 1 之后,结果完全不同。
为什么会这样?
说白了,数组名在大多数表达式中会「退化」成指向首元素的指针,但 &arr 取的是整个数组的地址,类型是 int (*)[5],也就是「指向包含 5 个 int 的数组的指针」。
所以:
arr + 1跳过一个 int(4 字节)&arr + 1跳过一个完整的数组(5 × 4 = 20 字节)
这个区别,在指针运算和内存操作时特别要命。
数组名什么时候「不是」指针?
我总结了一下,数组名真正「像指针」的场景其实很有限。下面这几种情况,数组名就是数组名,别把它当指针用:
- sizeof 运算符:
sizeof(arr)返回整个数组的字节数,不是指针的大小 - & 取地址符:
&arr得到的是整个数组的地址,类型是数组指针 - 数组初始化:
char str[] = "hello";这里的 str 是数组,不是指针
除此之外,在函数传参时,数组名确实会退化为指针。这也是为什么你在函数里用 sizeof 拿不到数组长度的原因。
int arr[] 和 int *arr 是完全等价的。编译器会把前者当作后者处理。所以别指望在函数内部用 sizeof 获取数组长度——你拿到的永远是 4 或 8(指针大小)。
&arr 和 arr 的类型差异
这个知识点,我建议你死记硬背也要背下来。因为一旦搞混,指针偏移就会全盘出错。
| 表达式 | 类型 | 含义 | +1 偏移量 |
|---|---|---|---|
arr |
int* |
指向首元素的指针 | 4 字节 |
&arr[0] |
int* |
指向首元素的指针 | 4 字节 |
&arr |
int (*)[5] |
指向整个数组的指针 | 20 字节 |
你看,arr 和 &arr[0] 是等价的,但 &arr 完全是另一回事。
用 SVG 理清关系
下面这张图,把数组名、指针、地址的关系画清楚了。我建议你多看几遍,把这张图刻在脑子里。
避坑指南:我踩过的那些坑
我曾经在写一个环形缓冲区时,用 &arr 做指针偏移,结果缓冲区索引全乱了。调试了一下午才发现,&arr + 1 跳过了整个数组,而不是一个元素。
从那以后,我给自己定了个规矩:
- 如果只是想遍历数组,用
arr + i或&arr[i] - 如果确实需要整个数组的地址(比如传给某个需要二维数组指针的函数),才用
&arr - 永远不要在函数内部对数组参数用
sizeof——你拿到的永远是 4 或 8
总结一下
数组名不是指针,但大多数时候表现得像指针。记住三点:
arr和&arr的值相同,但类型不同,导致指针运算结果不同sizeof(arr)返回整个数组大小,sizeof(ptr)返回指针大小- 函数参数中的数组声明会退化为指针,别指望在里面用 sizeof 拿长度
嗯,这个知识点就讲到这里。下次面试再被问到「数组名是不是指针」,你应该知道怎么答了吧?
公众号:蓝海资料掘金营,微信deep3321