i=i++引發的思考
博學,切問,近思--詹子知 (https://jameszhan.github.io)
在網上看到網友發的帖子,對於程序 int j=2,m=2;m+=(j++)+(++j)+(j++); 執行後結果有爭議, 甚至在不同的語言環境下,它們執行的結果也截然不同。
其實,同類型的問題有很多,最出名就數i=i++了,在Java中,無論執行多少次這樣的語句,i的值都不會改變,而在C/C++中卻能夠順利的自加。究其根源,這跟他們編譯後生成的字節碼或機器代碼的方式有關。在Java中,它的執行過程可以等價於:
int j = i;
i = i + 1;
i = j;
而在C/C++中,自加操作需要等到整個語句執行完才執行,執行過程是這樣的:
int j = i;
i = j;
i = i + 1;
對於第一個例子,在java環境中,本段程序執行後的結果是i=5,m=12,而在C中執行的結果卻是i=5,m=11。這讓我搞了幾年Java的人很是詫異。Java的思維習慣比較符合我們的習慣,對於語句中每個表達式分別求值,然後把結果相加。執行過程等價於:
a = j++;
b = ++j;
c = j++;
m += a + b + c;
最後的結果是:m = m + a + b + c = 2 + 2 + 4 + 4 = 12, 而 i自加了3次,最後的值為5。
在c/c++中,i++和++i在語句中的執行是有很大差異的,i++運算符的意義是執行完當前語句之後,將目標值加1,而++i是在執行語句之前先完成自加操作。所以其執行過程等價於:
j++;
a = j;
b = j;
c = j;
m += a + b + c;
j++;
j++;
最後的結果是: m = m + a + b + c = 2 + 3 + 3 + 3 = 11, i同樣自加了3次,最後值為5。
對知識的理解一定不能隻停留於表象,如果有可能,盡量去索本求源,事實上,這段程序匯編後的代碼也驗證了c語言的規則。
main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp movl $2, -12(%ebp) movl $2, -8(%ebp) addl $1, -8(%ebp) movl -8(%ebp), %eax addl -8(%ebp), %eax addl -8(%ebp), %eax addl %eax, -12(%ebp) addl $1, -8(%ebp) addl $1, -8(%ebp) addl $16, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret
為了更好的驗證這個規則,我們可以寫一段更複雜一點的語句,看看他匯編後的程序是什麼樣子。
int m, i = 2; m = (++i) + (--i) + (i++) + (++i) + (i--) + (++i);
這段程序,在Java中,m 的值是19,i 的值4. 執行的過程就是從前往後,每個表達式逐步執行,m=3+2+2+4+4+4 = 19。
而在C/C++中的執行過程是這樣的,按照之前的規則,先做3次自加操作和一次自減操作,在把6個表達式的值相加,所以m=4+4+4+4+4+4=24,最後分別執行一次自加和自減操作。然而事實上,運行後的結果卻是16。這又是為什麼呢?我們先看一下,匯編後的代碼。main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $36, %esp movl $2, -12(%ebp) addl $1, -12(%ebp) subl $1, -12(%ebp) movl -12(%ebp), %eax addl -12(%ebp), %eax addl -12(%ebp), %eax addl $1, -12(%ebp) addl -12(%ebp), %eax addl -12(%ebp), %eax addl $1, -12(%ebp) addl -12(%ebp), %eax movl %eax, -8(%ebp) addl $1, -12(%ebp) subl $1, -12(%ebp)
用C代碼還原是這樣的:i++; i--; tmp += i; tmp += i; tmp += i; i++; tmp += i; tmp += i; i++; tmp += i; m = tmp; i++; i--; ,可以看出m=2+2+2+3+3+4=16。很顯然,在求值的過程中,++i的操作和求整個表達式值的操作交替執行了,但是i--和i++的操作還是等到求值完成後再去做的。
可見,對於上述表達式的值,我們是很難預期的,即使同是C,很有可能在不同的編譯器環境下得到的結果也是不同的。避免的方法隻有一個,不要寫這種容易引起混淆的代碼,雖然多了幾行代碼,但是卻可以讓人看的更明白,代碼不是寫給計算機的,而是寫給後來人看的,所以為了節省自己和他人的時間,請規範你自己的代碼。
最後更新:2017-04-02 04:00:25