指针数组运算后用[]访问异常的原因及运算符优先级解析
咱们一步步捋清楚每一行代码对指针和数组的影响,就能明白为什么会出现那些“不符合预期”的结果了。
一、原代码的指针变化与越界问题
首先看初始状态:
int * pointer_arr = new int [3] {10, 11, 12};
此时pointer_arr指向数组的第一个元素(索引0,值10),数组内存布局是:
| 索引0 | 索引1 | 索引2 |
|---|---|---|
| 10 | 11 | 12 |
第一步:std::cout << * pointer_arr ++ << std::endl;
这里要注意运算符优先级:后缀++的优先级高于一元*,实际等价于*(pointer_arr++):
- 先返回
pointer_arr当前指向的值(10),输出到控制台; - 然后
pointer_arr自增,指向数组的索引1(值11)。
现在数组内容没变,但指针位置已经偏移到索引1了。
第二步:std::cout << ++ * pointer_arr << std::endl;
一元++和*是同优先级,结合性从右到左,等价于++(*pointer_arr):
- 先解引用
pointer_arr,得到它指向的元素值(11); - 对这个值自增(变成12),然后输出12。
此时数组索引1的值更新为12,指针仍指向索引1:
| 索引0 | 索引1 | 索引2 |
|---|---|---|
| 10 | 12 | 12 |
第三步:++ * pointer_arr;
和第二步逻辑一致,解引用后对值自增,索引1的元素从12变成13,指针依旧停在索引1。
第四步到第六步:[]访问的核心误区
这里你犯了一个关键错误:误以为pointer_arr还指向数组开头,但它早就已经指向索引1了!
pointer_arr[0]等价于*(pointer_arr + 0),也就是指针当前指向的元素(索引1,值13),输出13,符合预期;pointer_arr[1]等价于*(pointer_arr + 1),也就是指针往后跳1个元素,指向索引2(值12)——你预期的11是原数组索引1的值,但现在指针不在开头,根本访问不到原索引0的元素;pointer_arr[2]等价于*(pointer_arr + 2),也就是指针往后跳2个元素,指向索引3——这已经超出了数组的范围(数组只有索引0、1、2三个元素)!访问越界内存属于未定义行为,输出0只是编译器生成的随机垃圾值,换个环境可能输出别的数。
这就是最后一个元素输出0的根本原因:越界访问了不属于数组的内存。
二、有无括号的差异:(*pointer_arr)++ vs *pointer_arr++
当你把第一行改成std::cout << ( * pointer_arr ) ++ << std::endl;时,括号直接改变了运算顺序:
- 先解引用
pointer_arr,得到索引0的元素值(10); - 对这个值自增(变成11),然后输出10;
- 关键:
pointer_arr本身没有被修改,依旧指向数组开头!
后面的所有操作都是基于指向数组开头的指针:
++*pointer_arr修改索引0的元素,从11变成12,输出12;++*pointer_arr再把索引0的元素改成13;- 之后的
pointer_arr[0]是索引0的13,pointer_arr[1]是索引1的11,pointer_arr[2]是索引2的12,完全符合预期。
简单总结两者的核心区别:
*pointer_arr++:指针自增,解引用的是自增前的指针值;(*pointer_arr)++:元素值自增,指针位置全程不动。
三、添加std::cout << * ++ pointer_arr << std::endl;后的异常
在输出12的行后加这行代码,逻辑是:
++pointer_arr:先把指针从索引1自增到索引2;*解引用,得到索引2的元素值12,输出12;- 此时
pointer_arr指向索引2。
后面的pointer_arr[1]等价于*(索引2 +1) = 索引3,又是越界访问,所以输出随机值-1342177820——这依旧是未定义行为导致的垃圾值。
总的来说:指针是会“移动”的,一旦你用pointer_arr++修改了指针本身,后续的[]访问都是基于新的指针位置,千万不要默认它还在数组开头!运算符优先级和括号的作用直接决定了你是在修改指针,还是修改指针指向的元素值。
内容的提问来源于stack exchange,提问作者lambda

