前置++与后置++之一道简单的题目引发的思考
引言
昨晚一時(shí)興起,我腦子就問(wèn)自己下面的代碼會(huì)輸出什么,也不知道我腦子為什么有這個(gè)代碼模型,只是模糊的有些印象:
#include <stdio.h> #include <stdlib.h>int main(int argc,char** argv) {int i=3,j;j=(i++)+(i++)+(++i);printf("i = %d, j = %d\n",i,j);exit(0); }您會(huì)怎樣考慮這個(gè)問(wèn)題呢?您不運(yùn)行這個(gè)程序能準(zhǔn)確地說(shuō)出答案嗎?我猜想肯定有大部分人不能肯定且準(zhǔn)確地說(shuō)出答案!如果您不能,這篇文章就是為你準(zhǔn)備的,保證您看完之后豁然開(kāi)朗!請(qǐng)細(xì)看下文,outline如下:
1、諸君的回答
1.1、A君的回答
1.2、B君的回答
1.3、C君的回答
1.4、D君的回答
2、編譯器的輸出
2.1、Visual Studio的輸出
2.2、GCC的輸出
2.3、Visual C++ 2010的輸出
3、分析
3.1、gcc編譯器上的分析
3.2、分析gcc編譯之后的匯編代碼
3.3、vs編譯器上的分析
3.4、分析VS編譯之后的匯編代碼
4、擴(kuò)散思維
4.1、思維放射
4.2、VS的輸出
4.3、GCC的輸出
5、感慨
1、諸君的回答
我拿這道題目問(wèn)了幾個(gè)人,他們的答案不盡相同。
1.1、A君的回答
因?yàn)閕 = 3,故依次i++=4,i++=5,++i=6,i最后輸出為i = 6;但是由于前面兩個(gè)++是后置++,最后一個(gè)++是前置++,故j = 3+4+6 = 13。
1.2、B君的回答
因?yàn)閕 = 3,故第一個(gè)i++后為4,第二個(gè)i++后為5,接著做i+i操作 = 5+5=10,最后與(++i)相加 = 10+6=16。
1.3、C君的回答
因?yàn)閕 = 3,故依次i++=4,i++=5,++i=6,i最后輸出為i = 6;但是第一i、第二個(gè)i的++是后置++,先進(jìn)行i+i操作,然后進(jìn)行兩次i++后置操作,故等價(jià)于(i)+(i) = 3+3=6,i++,i++,最后與++i=6相加等于12。
1.4、D君的回答
因?yàn)閕 = 3,故依次i++=4,i++=5,++i=6,i最后輸出為i = 6;但是前面兩個(gè)++都是后置++,故先做i+i+(++i)操作,然后才在i++,i++操作,第三個(gè)++是前置++,故等價(jià)于 i+i+(++i)=3+3+4=10,i++,i++。
到底哪個(gè)人說(shuō)得對(duì)呢?
2、編譯器的輸出
首先讓我們先來(lái)看看編譯器會(huì)輸出什么?
2.1、Visual Studio的輸出
運(yùn)行環(huán)境:Win7+VS2005 or VS2010,輸出如下圖所示:
2.2、GCC的輸出
運(yùn)行環(huán)境:Ubuntu 10.04+gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3,運(yùn)行結(jié)果如下:
2.3、Visual C++的輸出
運(yùn)行環(huán)境:Win7+VC2010,輸出和VS一樣,及i = 6 & j = 12
看到這里你肯定想問(wèn)why? why?? why???
3、分析
重編譯器的輸出結(jié)果來(lái)看貌似C君、D君的分析都是對(duì)的,這種差異跟編譯器有直接的關(guān)系,因?yàn)閷?duì)于這個(gè)表達(dá)式怎么編譯還沒(méi)有形成標(biāo)準(zhǔn),編譯器的結(jié)合方向不同,答案因此會(huì)有所不同。而且當(dāng)然還包括運(yùn)算符的優(yōu)先級(jí)等。其實(shí)頂多算C君答對(duì)了一部分,其他幾個(gè)人的回答都是錯(cuò)的,詳情見(jiàn)下面的分析。
3.1、gcc編譯器上的分析
(i++)+(i++)+(++i) <=> i+i+(++i); i++; i++;即如果表達(dá)式中含有i++,一律替換成i,然后在表達(dá)式之后進(jìn)行i++操作。
這樣的話上面的代碼就可以很好的理解了,即3+3+4=10。
3.2、分析gcc編譯之后的匯編代碼
可以對(duì)gcc編譯之后的執(zhí)行文件進(jìn)行反編譯分析驗(yàn)證正確性。在Linux下面可以用objdump –d xxx(執(zhí)行文件)命令反匯編執(zhí)行文件。反編譯之后可以看到如下圖所示的代碼:
說(shuō)明:Linux下采用的是AT&T的匯編語(yǔ)法格式,Windows下面采用的是Intel匯編語(yǔ)法格式。二者的主要區(qū)別在于:
Intel:第一個(gè)是目的操作數(shù),第二個(gè)是源操作數(shù)
AT&T:第一個(gè)是源操作數(shù),第二個(gè)是目的操作數(shù)
AT&T:寄存器前邊要加上%,立即數(shù)前要加上$
Intel:沒(méi)有這方面的要求
Intel:基地址使用[]
AT&T: 基地址使用()
比如:intel中 mov ax,[bx]
AT&T中 movl (%eax),%ebx
AT&T中操作碼后面有一個(gè)后綴字母:“l(fā)” 32位,“w” 16位,“b” 8位
Intel卻使用了在操作數(shù)前面加dword ptr, word ptr, byte ptr的格式
例如:mov al,bl (Intel)
movb %bl %al (AT&T)
下面我們重點(diǎn)分析紅框中的代碼:
movl $0x3 ,0x1c(%esp):將3賦給i,即i=3
mov 0x1c(%esp) ,%eax:將esp中的i放到eax中
add %eax ,%eax:進(jìn)行i+i操作,即3+3
addl 0x1 ,0x1c(%esp):對(duì)i進(jìn)行加1操作,即表達(dá)式中的(++i)
add 0x1c(%esp),%eax:將eax中i+i的結(jié)果6,加上++i之后的i,即6+4=10
addl 0x1 ,0x1c(%esp):對(duì)i進(jìn)行加1操作,即表達(dá)式中的(i++)
addl 0x1 ,0x1c(%esp):對(duì)i進(jìn)行加1操作,即表達(dá)式中的(i++)
至此關(guān)鍵代碼已經(jīng)分析完成,由此可見(jiàn)我們之前對(duì)gcc編譯器上的分析是正確的。
3.3、vs編譯器上的分析
(i++)+(i++)+(++i) <=>(++i)+i+i; i++; i++;即如果表達(dá)式中含有前置++i,首先執(zhí)行++i操作;表達(dá)式中的i++,一律換成i,然后執(zhí)行加法操作;最后在進(jìn)行i++操作。
這樣的話上面的代碼就可以很好的理解而來(lái),即首先執(zhí)行++i,i變?yōu)?了;然后進(jìn)行i+i+i=4+4+4;i++,i++。
其實(shí)對(duì)于VS/VC2010編譯器中的可以總結(jié)為:當(dāng)用于四則運(yùn)算時(shí),前置++/–的運(yùn)算優(yōu)先級(jí)最高,后置++/–的運(yùn)算優(yōu)先級(jí)最小,其它的居中。(跟你書(shū)上看到是不是不同!)
3.4、分析VS編譯之后的匯編代碼
用W32Dasm反匯編vs編譯生成的exe文件,追蹤代碼。我們可以看到如下圖所示的代碼:
反匯編后的代碼
下面重點(diǎn)分析一下框中代碼:
mov [ebp-08],3:將3賦給i,即i=3
mov eax,dword ptr [ebp-08]:將ebp中的i的值放到eax中,是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。dword ptr表示這是一個(gè)雙字指針,即所要尋址的數(shù)據(jù)是一個(gè)雙字(4字節(jié))
add eax,1:對(duì)eax中的i進(jìn)行加1操作
mov dword ptr [ebp-08] ,eax:將eax中的i賦給ebp中i,即將i加1之后的值賦給i,也即達(dá)到i=i+1的效果
mov ecx,dword ptr [ebp-08]:將ebp中的i放到ecx中
add ecx,dword ptr [ebp-08]:將ebp中的值加上i,即4+4
add ecx,dword ptr [ebp-08]:將ebp中的值加上i,即4+4+4
mov dword ptr [ebp-14],ecx:將ecx中的值賦給j
mov edx,dword ptr [ebp-08]:將i放到edx中
add edx,1:對(duì)edx中的i進(jìn)行加1操作
mov dword ptr [ebp-08] ,edx:將edx中的i賦給ebp中i,即將i加1之后的值賦給i,也即達(dá)到i=i+1的效果
mov eax,dword ptr [ebp-08]:將i放到eax中
add eax,1:對(duì)eax中的i進(jìn)行加1操作
mov dword ptr [ebp-08] ,eax:將eax中的i賦給ebp中i,即將i加1之后的值賦給i,也即達(dá)到i=i+1的效果
至此,上面表達(dá)式的關(guān)鍵運(yùn)算部分已經(jīng)分析完成。從這里可以知道,上面我們地VS編譯器的分析是正確的。
4、發(fā)散思維
可以說(shuō)通過(guò)上面那么篇幅的介紹,我們對(duì)涉及前置++和后置++的加法運(yùn)算表達(dá)式的計(jì)算過(guò)程有了一個(gè)清楚的認(rèn)識(shí),下面就我們發(fā)散一下我們的思維,釋放我們的能量。
4.1、思維放射
您看下面的代碼會(huì)輸出什么,現(xiàn)在知道了吧!
#include <stdio.h> #include <stdlib.h>int main(int argc,char** argv) {int i=3,j=3,k=3,l=3,m=3,n=3,result1,result2,result3,result4,result5,result6;result1=(++i)+(++i);printf("i = 3\n");printf("result1= (++i)+(++i) = %d\n\n",result1);result2=(j++)+(j++); printf("j = 3\n");printf("result2= (j++)+(j++) = %d\n\n",result2);result3=(++k)+(++k)+(++k);printf("k = 3\n");printf("result3= (++k)+(++k)+(++k) = %d\n\n",result3);result4=(++l)+(++l)+(l++);printf("l = 3\n");printf("result4= (++l)+(++l)+(l++) = %d\n\n",result4);result5=(m++)+(m++)+(m++);printf("m = 3\n");printf("result5=(m++)+(m++)+(m++) = %d\n\n",result5);result6=(n++)+(++n)+(n++);printf("n = 3\n");printf("result6=(n++)+(++n)+(n++) = %d\n\n",result6);exit(0); }請(qǐng)不看結(jié)果先自己分析一下,然后和結(jié)果對(duì)比!
4.2、VS的輸出
運(yùn)行環(huán)境:Win7+VS2005 or VS2010,輸出如下圖所示:
4.3、GCC的輸出
運(yùn)行環(huán)境:Ubuntu 10.04+gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3,運(yùn)行結(jié)果如下:
根據(jù)前面我們挖掘到的規(guī)則,我們可以得到result3之外所有其它答案。最后,還有一點(diǎn)要說(shuō)明的是:gcc中的加法運(yùn)算表達(dá)死中,是按照從左到右按順序,如果運(yùn)算符兩邊有++i操作數(shù),就先進(jìn)行++i操作,然后進(jìn)行加法運(yùn)算;vs中的加法運(yùn)算表達(dá)式中,則不一樣,只要表達(dá)式中有++i操作數(shù),就要先計(jì)算,最后才是進(jìn)行加法運(yùn)算。這也是為什么result3不同的原因!加法運(yùn)算可以擴(kuò)展到減法、乘法、除法運(yùn)算和前置–、后置–。但是如果是四則混合運(yùn)算還要考慮加、減、乘、除的優(yōu)先級(jí)問(wèn)題。
5、感慨
通過(guò)這么多分析,我們可以算得上是對(duì)涉及++、–的運(yùn)算表達(dá)式計(jì)算過(guò)程有了透徹理解!我在挖掘這個(gè)計(jì)算過(guò)程的路上,可是化了不少功夫也在剛開(kāi)始分析匯編代碼時(shí)遇到了一些困難,但這顆求知的心,推動(dòng)著我堅(jiān)持要去弄清楚它!最后我想說(shuō):請(qǐng)不要寫(xiě)這種語(yǔ)句!理由很簡(jiǎn)單,它既不好理解又不好維護(hù),最重要的是它的結(jié)果會(huì)因編譯器的不同而不同。
作者:吳秦
出處:http://www.cnblogs.com/skynet/
本文基于署名 2.5 中國(guó)大陸許可協(xié)議發(fā)布,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名吳秦(包含鏈接)
總結(jié)
以上是生活随笔為你收集整理的前置++与后置++之一道简单的题目引发的思考的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java欧冠抽签,欧冠抽签吐槽:最大的“
- 下一篇: java script中==和===_J