首先看這段代碼:
#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]
的值。