提起i++(i--)和++i(--i),相信大家非常熟悉。兩者的區別是:前者是先賦值,然後再自增或自減;後者是先自增或自減,後賦值。但是當printf函式與多個自增自減表示式結合起來,編譯器實現
i++(i--)和++i(--i)
的原理你真的了解嗎?今天,我們從一道有意思的題目揭開printf多個自增自減表示式底層實現的面紗......
源程式
按照我們的理解,這道題目太簡單了嘛,不就是int main()
0,0,0。因為函式傳參從右向左,i=0,先進行i++,i先賦值,那麼取值為0,然後自增,i變成1。接著--i,先自減,i變成0,再賦值,取值為0。最後i++,先賦值,取值為0,再自增,i變成1。理完思路,你不禁會想,這道題沒有一點意思,哈哈,先別高興太早,等執行完結果驗證一下......
結果是:
0 1 0,與我們分析的結果有出入,回過頭再去分析我們的思路,沒有問題啊!那麼問題出在**?看來我們只知其一不知其二,不如反彙編之後,從彙編指令來分析編譯器i++,++i的底層實現原理,來解決我們的問題。
彙編指令
int i=0;
00ea13be mov dword ptr [i],0
printf("%d %d %d\n",i++,--i,i++);
00ea13c5 mov eax,dword ptr [i]
00ea13c8 mov dword ptr [ebp-0d0h],eax
00ea13ce mov ecx,dword ptr [i]
00ea13d1 add ecx,1
00ea13d4 mov dword ptr [i],ecx
00ea13d7 mov edx,dword ptr [i]
00ea13da sub edx,1
00ea13dd mov dword ptr [i],edx
00ea13e0 mov eax,dword ptr [i]
00ea13e3 mov dword ptr [ebp-0d4h],eax
00ea13e9 mov ecx,dword ptr [i]
00ea13ec add ecx,1
00ea13ef mov dword ptr [i],ecx
00ea13f2 mov esi,esp
00ea13f4 mov edx,dword ptr [ebp-0d0h]
00ea13fa push edx
00ea13fb mov eax,dword ptr [i]
00ea13fe push eax
00ea13ff mov ecx,dword ptr [ebp-0d4h]
00ea1405 push ecx
00ea1406 push offset string "%d %d %d\n" (0ea573ch)
00ea140b call dword ptr [__imp__printf (0ea82bch)]
00ea1411 add esp,10h
00ea1414 cmp esi,esp
00ea1416 call @ilt+310(__rtc_checkesp) (0ea113bh)
彙編指令的分析
(1)先將0,放入到變數i中。
00ea13be mov dword ptr [i],0
(2)將i的值0,放入到暫存器eax,再將eax的值放入到乙個位址為[ebp-0d0h]的臨時變數中,臨時變數中的值為0。接著又把變數i的值0,放入到暫存器ecx中,ecx加1,值為1,最後將ecx的值放入到變數i中,此時i值為1。
00ea13c5 mov eax,dword ptr [i]
00ea13c8 mov dword ptr [ebp-0d0h],eax
00ea13ce mov ecx,dword ptr [i]
00ea13d1 add ecx,1
00ea13d4 mov dword ptr [i],ecx
(3)將i值放到暫存器edx中,此時edx的值為1,再將edx的值減1,edx值為0,再將edx的值放入變數i中,此時i值為0。
00ea13d7 mov edx,dword ptr [i]
00ea13da sub edx,1
00ea13dd mov dword ptr [i],edx
(4)將變數i的值放入暫存器eax中,此時eax的值為0,將eax的值放入到位址為[edp-0d4h]的臨時量中,此時臨時量的值為0。接著又把變數i的值,放入到暫存器ecx中,ecx的值為0,再將ecx的值加1,放到變數i中。
00ea13e0 mov eax,dword ptr [i]
00ea13e3 mov dword ptr [ebp-0d4h],eax
00ea13e9 mov ecx,dword ptr [i]
00ea13ec add ecx,1
00ea13ef mov dword ptr [i],ecx
(5)將暫存器esp的值放入esi中
00ea13f2 mov esi,esp
(6)將位址為[ebp-0d0h]臨時量的值0,放入到暫存器edx中,再將edx的值0壓入棧中。
00ea13f4 mov edx,dword ptr [ebp-0d0h]
00ea13fa push edx
(7)將變數i的值1,放入暫存器eax中,再將eax的值1壓入棧中。
00ea13fb mov eax,dword ptr [i]
00ea13fe push eax
(8)將位址為[ebp-0d4h]臨時量的值0,放入到暫存器ecx中,再將edx的值0壓入棧中。
看完彙編**,我們的問題就解決了!00ea13ff mov ecx,dword ptr [ebp-0d4h]
00ea1405 push ecx
00ea1406 push offset string "%d %d %d\n" (0ea573ch)
00ea140b call dword ptr [__imp__printf (0ea82bch)]00ea1411 add esp,10h
00ea1414 cmp esi,esp
00ea1416 call @ilt+310(__rtc_checkesp) (0ea113bh)
總結:原來在呼叫printf函式時,先自右向左遍歷所有引數,同時進行引數的計算,最終自右向左壓參入棧。在上面彙編指令分析的過程,(2)
(3)(4)步完成引數的遍歷和計算的操作,(6)(7)(8)步完成自右向左壓參入棧的操作。所以對於i++(i--),編譯器必須先將變數i儲存到一
個臨時量中,保證不受引數運算的影響,再進行自加/自減操作,最終將臨時量的值壓入棧中。對於--i(++i),對變數i直接進行自加/自減操作,無需保
存到臨時量中,最終直接去i的記憶體中取值進行壓棧。值得注意的是:如果在--i之後對i進一步進行別的操作,那麼最終壓棧的資料很可能不是我們在--i
或者++i時存入i的記憶體的值。正如本題中,i++之後,i的值變為1,在進行--i時,i記憶體的值變為0,之後又對i進行i++,i記憶體的值變為1,所以最終--i壓入
棧中的引數是1,並非我們所想的0。
啟示:當**分析結果與實際執行結果不一致時,善用彙編**去分析底層實現原理。
同級表示式的運算順序 自增自減 cin cout
前言 本文一些內容是根據自己理解來寫的,沒有完全查證,不過應該也差不多了。要想完全理解表示式,還是有很多東西要理解的,可謂博大精深。在中的運算子和表示式 一文中,我已經提到了理解表示式最核心的幾個概念,編譯方式,運算子優先順序,運算子結合性,還有特殊的自增自減運算子等等。在運算子過載 一文中,我也提...
自增「 」和自減「 」
int x 10 int j j x 看這邊 system.out.println x int x 10 int j x x 看這邊 system.out.println x 第乙個會輸出11,第二個會輸出10。這是今天偶然看到的,而這個解釋聽起來比較容易理解 x 是乙個表示式,它的值是x,在這個表...
正解自增 自減
許多語言都有自增 自減運算子。下面以c語言為例,將此類運算子的運算特徵正解一下 一 先看字尾 b 2 a b 計算過程 1.先計算表示式b 的值,結果為2 即將b自增前的值作為表示式b 的值 2.再將表示式b 的值賦值給a,即a得到2 3.最後將b自增後的值賦值給b,即b為3 注意 在此過程中,沒有...