首先看这段代码:
#include <stdio.h>
int main()
{
int a[] = {0, 2, 4, 6, 8};
int *p = a;
// 打印头两位的内存地址
printf("\n%d %d", p, p+1 ); // 1143884624 1143884628
// 此处两个操作都返回的是*p的值(即a[0]),没有修改过p的指向
printf("\n%d %d", *(p++), *p ); // 0 0
// 再次打印*p,发现p的指向变了
printf("\n%d", *p ); // 2
// 查看地址,果然和第一行中p+1的内存地址相同
printf("\n%d", p ); // 1143884628
// 最后,作为对比查看原数组,并没有任何值发生改变
printf("\n%d %d %d", a[0], a[1], a[3]); // 0 2 4
return 0;
}
看起来是个比较诡异且反直觉的UB问题,但其实不是。
要解释这个问题,首先是要说明“前后自增”的汇编方式:
观察下面的代码:
#include <stdio.h>
int main()
{
int a = 1;
int b = a++;
printf("%d %d", a, b); // 很显然会打印 2 1
// 如果改成++a 会打印 2 2
// 根据算符的结合顺序,改成 b = (a++),结果也是 2 1
return 0;
}
从汇编的角度出发解释这个反直觉现象:
int b = a ++
:
a
的值被取出,放到eax
;eax+1
放到edx
;a=edx
b=eax
1~4
步即:b
取a
原值,a
值加一。
int b = ++a
:
a=a+1
;a
的值被取出,放到eax
;b=eax
1~3
步即:a
值加一后b
再取a
的值。
其次,++
和*
都是右结合,所以从结合的角度来说,由于++
优先级大于*
,即*p++
和*(p++)
二者必然等价。如果你能理解 int d = *p++
取的是a[0]
的值,那么根据等价关系,实际上*(p++)
也是取到a[0]
,而:
printf("\n%d %d", *(p++), *p ); // 0 0
之所以打印了两次a[0]
,在printf
中,同一地址只取值一次,执行顺序并不是括号内从左到右的,因此本行中p
始终指向a[0]
的地址,但是下一行再次打印*p
,由于*(p++)
(或*p++
)的存在,此时应该打印p[1]
的值。