a的值應該加一還是不變?
結論:不同的編譯器,會得出不同的結果。
因為a = a ++這種表達式,在C語言規(guī)范中是屬于未定義的行為(Undefined behavior)。
以下面這段代碼為例,在Linux上打印0x1234,在Windows上打印0x1235。
下面分別在Windows和Linux上演示,并從匯編的角度,詳細講解一下。
Windows(Visual Studio 2015)在Windows上,用VS2015編譯并運行,結果如下:
看一下反匯編:
藍色方框內(nèi)指令 mov dword ptr[a], 1234h 給變量a賦初值,也就是0x1234。
紅色方框內(nèi)兩條指令,看起來挺有意思:
第一條:mov eax, dword ptr[a] 把變量a的值加載到寄存器eax中。
第二條:mov dword ptr[a], eax 又把寄存器eax的值,存放到變量a中。
這兩條指令時沒有任何意義的。
綠色方框內(nèi)的三條指令:
第一條:mov ecx, dword ptr[a] 把變量a的值加載到寄存器ecx中,也就是0x1234。
第二條:add ecx, 1 把ecx的值加1,此時ecx = 0x1234 + 1 = 0x1235。
第三條:mov dword ptr[a], ecx 把寄存器ecx的值存放到變量a中,此時a的值是0x1235,這也是a最終的值。
不難看出,VS2015上,表達式 “a = a ++”被翻譯成下面的偽代碼表示:
a = 0x1234;
eax = a;
a = eax;
ecx = a;
ecx = ecx + 1;
a = ecx;
所以,最終變量a的值是0x1235。
Linux(GCC 8.3.0)同樣的程序,在Ubuntu上用GCC 8.3.0編譯,并運行,結果為0x1234。如下圖:
把GCC編譯生成的目標文件進行反匯編,如下圖:
圖中,關鍵指令已經(jīng)標記出來了,應該比較容易理解了。不難看出,表達式“a = a ++”被GCC翻譯成如下偽代碼:
a = 0x1234;
eax = a;
edx = eax + 1;
a = edx;
a = eax;
因此,最終變量a的值是0x1234。
sequence pointC語言中有個重要的概念 - “sequence point”,有的翻譯為順序點或序列點,還有翻譯為執(zhí)行點的。C語言規(guī)范要求,編譯器必須保證,在某個sequence point上,它之前的所有表達式的計算都已經(jīng)確定,而它之后的所有表達式計算都尚未開始。
C語言規(guī)范中,對各種表達式定義了一系列的sequence point,感興趣的童鞋可以翻閱C語言規(guī)范。
對于“a = a ++;”這個表達式,C語言規(guī)范定義的sequence point是最后的分號“;”。但是,這個表達式中,可能改變a的值的地方卻有兩個:一個是對等號左邊的a賦值的操作,一個是等號右邊的a的自加操作。
這兩個操作之間,并沒有明確定義的sequence point。因此,才導致了不同的編譯器采用不同的計算順序,得出不同的結果。
其實,如果編譯時開啟告警選項,編譯時是會有告警信息的,比如GCC和Clang:
建議C語言規(guī)范中,列舉了很多不確定的行為,主要有:
Unspecified behaviorUndefined behaviorImplementation-defined behaviorLocale-specific behavior總之,有很多語法規(guī)則,C語言規(guī)范并沒有定義一個確定的輸出結果,不同的編譯器、版本、運行時環(huán)境、OS等都可能會產(chǎn)生不同的結果。
程序開發(fā)時,應該盡量避免使用這種容易產(chǎn)生歧義的語法規(guī)則。否則,很可能會在不同的環(huán)境上得到不同的結果,而且調試起來也相當費時!
對本文有疑問歡迎留言討論!覺得有用請點個贊!
對編譯器、OS內(nèi)核、虛擬化、性能調優(yōu)等技術感興趣的童鞋,歡迎關注!