一切都是從這開始的
一個大一學弟通過QQ給我發來一個C++的題:
int c = 8, b = 3;
c += c-- | ++b;
問c的值是多少。通過筆算得到c為19,然后隨手建了個C#控制台項目跑了一下,悲劇了。。。C#輸出的為20。重新筆算一遍還是19啊,趕緊重新建了一個C++控制台項目跑出的結果為19。到底為什么C++和C#會不一樣呢?
求證1
通過網上查資料得知,是C#求值順序的問題,具體是怎么樣的情況呢?我們來反匯編一下:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 33 (0x21)
.maxstack 4
.locals init ([0] int32 c,
[1] int32 b)
IL_0000: nop
IL_0001: ldc.i4.8
IL_0002: stloc.0
IL_0003: ldc.i4.3
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldloc.0
IL_0007: dup
IL_0008: ldc.i4.1
IL_0009: sub
IL_000a: stloc.0
IL_000b: ldloc.1
IL_000c: ldc.i4.1
IL_000d: add
IL_000e: dup
IL_000f: stloc.1
IL_0010: or
IL_0011: add
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: nop
IL_001a: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_001f: pop
IL_0020: ret
} // end of method Program::Main
通過IL代碼我們可以清晰的看到他的計算過程:
- 0~4行 為變量賦值Call Stack里0為變量c值為8,1為變量b值為3
- 5行 緩存了c的值,放到了Evaluation Stack里,緩存的值為8
- 6行 緩存了一個c值,緩存的值為8
- 7~a行 執行了c=c-1,此時c值為7
- b~f行 執行了b=b+1,並留了一個副本,值為4
- 10行 6行緩存的數8和b~f行中留的4做or,值為12
- 11行10行中結果12再加5行中緩存的8結果為20
- 12行存儲到變量c中
c += c-- | ++b; 等價於 c = c + (c-- | ++b);,通過反匯編我們可以看出:
- C#的求值順序為從左到右不會因為運算順序改變,等號右側第一個c的值在一開始就緩存了。
- c--在求值之后立刻就進行了結算,c變量此時值變為了7,但是c變量的值並不影響算式最終的結果,原因見1。
- 把算式變為c = c + (--c | ++b);得到的值為15(8+(7|4)),反匯編后觀察,結論同第一條,c--和--c只影響了括號中的運算結果。
根據上面結論我們把算式改成c = (c-- | ++b) + c;,得到的結果為19。
求證2
那么C++到底是怎么執行的呢?繼續,反編譯之:
(上圖反匯編的程序基於VS2013的C++ Debug編譯結果,GCC 4.6.1的反匯編代碼略有區別,執行過程一致,結論僅限定在這兩個編譯環境下)
C++的執行過程:
- 使用eax寄存器做b=b+1
- 使用ecx寄存器做c=8+(8|4)(此時b=4)
- 使用edx寄存器做c=c-1
從反匯編可以看出:
- C++不會緩存數值,C++也沒有規定求值順序。
- C++在計算的時候才取值,所以c = (c-- | ++b) + c;的結果還是19。
- C++是在整個算式結束的時候才進行的c--,也就是說之所以結果是19不是20,不是因為先算括號中的or造成最后的加法中的c為7,而是因為c--是在算式賦值結束后才進行結算。(老師們,你們教對了么?)
最后驗證一下,改變算式為c = c * 2 + (c-- | ++b);C++的輸出結果為27,8*2+12=28-1=27,反匯編也可以看到最后才執行的sub,圖就不上了。
最后
- 如果你在學C++,千萬不要用C#來驗證你的作業題答案。。。
- 就讓‘++’這種東西只出現在for語句中吧。。。