說到C/C++函數參數讀取順序,很多人都知道在入棧時是從右至左的,可是真的有那么簡單嗎?先看一個例子:
1 #include <cstdio> 2 3 int main() { 4 int a = 10; 5 printf("%d %d %d\n", a++, ++a, a); 6 return 0; 7 }
按照從右向左讀取,想當然的結果應該是:11 11 10,執行后a = 12。可是真的是這樣嗎?寫個程序驗證一下吧,於是就得到了下面的結果:
很奇怪,這是為什么呢?要搞清楚什么情況恐怕得從匯編代碼入手,那我們就看一下匯編代碼嘍:
通過查看匯編代碼,我們發現在參數入棧時順序的確是從右向左入棧,但是在入棧前先把參數列表里的表達式算一遍得到表達式的結果,最后再把這些運算結果統一入棧,這就解釋了為什么第三個參數a會輸出12,因為執行完a, ++a, a++后a = 12。那為什么第一項++a會輸出11呢,這就要看C++中的++運算符的實現機制了,通過上面的匯編代碼,可以看到:
++a對應的是: addl $0x1, 0x1c(%esp)
也就是說直接執行a+1;
a++對應的是: mov 0x1c(%esp), %eax
addl $0x1, 0x1c(%esp)
先把執行到此時的a的值備份到%eax,然后再執行+1操作,所有的表達式都執行完了,該將參數入棧了。怎么入呢?
不管是a,還是++a,入棧的代碼都是:mov 0x1c(%esp), %edx
mov %edx,0xc(&esp) | mov %edx,0x8(&esp)
也就是說直接從變量a所在的內存地址中取值。所以得到的肯定是所有運算都執行完的結果,也就是12。
而a++的入棧代碼是:mov %eax,0x4(&esp)
直接把之前備份的值入棧了,而備份的時候a所在地址中保存的值是11,所以%eax中的值是11。
這樣一來是不是就說通了。
總結下來有兩點:
1. 在將參數入棧前,編譯器會先把參數的的表達式都處理掉,哪怕這些運算會改變其中某些參數的值,
2.對於a++操作,編譯器會開辟一個緩沖區來保存當前a的值,然后再對a操作,取值時是從緩沖區取,而不是直接從a的內存地址里取。
最后再驗證一下理論,a = 10, printf("%d %d %d %d\n", a++, ++a, a, a++),結果應該是:12 13 13 10!
哦耶!對了,撒花!
好吧,參數列表中表達式的計算順序並沒有在C/C++中明確定義,每個編譯器可以自由發揮,有自己的實現。沒有給定編譯環境的話結果是未知的,不過至少最新版的g++與msvs2013的實現是一致的。