李云超
摘 要:C語言中提供的自增運算符能讓程序的書寫更加簡便和靈活,但如果運用不當也會使 程序的運行結果與預期大相徑庭,再者,現(xiàn)在的教程中并未從不同編譯器的編譯執(zhí)行角度進行深入剖析,使得初學者對一些語句有諸多困惑。本文通過實驗,利用不同的環(huán)境編寫調試程序,并對運行結果進行反匯編分析,總結出自增運算符在不同編譯環(huán)境中的運算規(guī)律以及在教學過程中的注意事項。
關鍵詞:C語言 自增 反匯編
中圖分類號:TN925 文獻標識碼:A 文章編號:1674-098X(2017)09(c)-0143-03
Abstract: The auto-increasing operator in C language can make writing more convenient and flexible, but it can also make the result different with expected if used undeserved, moreover, the tutorials do not analyze deeply in the angle of different compliers comping and executing, and that makes beginners be confused with some codes. This article analyze the result with disassembling in different compliers, then conclude the rules of auto-increasing and notices in the teaching process.
Key Words: C language; Auto-increasing; Disassemble
C語言歷經(jīng)了幾十年,成為經(jīng)典的編程語言,憑借語句的精煉、語法的靈活、對系統(tǒng)環(huán)境要求低、擁有較高的執(zhí)行效率而風靡整個世界,再經(jīng)過歷代改進,C語言發(fā)展至今非但未被淘汰,更是廣泛的應用于各種生產(chǎn)環(huán)境中,并且早已成為計算機專業(yè)學生的入門語言。
作為語句精煉的語言,比早期的匯編語言簡單了很多,一條語句能表示多個運算步驟或者多次寄存器變化,例如自增語句i++,能夠在參加其他運算的同時對本身值進行增加1而又不用增加額外的語句。但是如此精煉的語句卻在實際生產(chǎn)環(huán)境中會產(chǎn)生歧義導致嚴重的后果,這么說并不是說自增語句是洪水猛獸,而是由于C語言應用的場景太廣泛,早期發(fā)展標準尚未成形,不同編譯器廠商在語句執(zhí)行順序上尤其是復雜語句不盡相同,例如自增語句并列使用的時候,不同編譯器對它的解析方式會有不同:(++i)+(++i)+(++i),在不同的編譯器計算出的結果是不同的:在VC6.0中結果為10;在VC2012中結果為12;在TurboC中結果是9。那為什么會出現(xiàn)如此大的結果差異呢?首先了解一下自增運算符的運算規(guī)則。
1 自增運算符的運算規(guī)則
自增運算的規(guī)則:自增運算符是一元運算符,它的作用是使變量的值增1。運算符出現(xiàn)在變量的前面稱為前綴運算符,運算符出現(xiàn)在變量的后面稱為后綴運算符。前綴自增運算符在程序執(zhí)行過程中遵循“先自增后運算”的規(guī)則,后綴自增運算符遵循“先運算后自增”的規(guī)則。同理,自減運算符也是一樣[1]。下面用兩個簡單的實驗來實際運行一下自增運算符:
實驗1:
int main( ){
int a=8,b;
b=a++; /*后綴形式*/
printf(“a=%d, b=%d ”,a,b);
return 0;
}
實驗2:
int main( ){
int a=8,b;
b = ++a; /*前綴形式*/
printf(“ a=%d,b=%d”,a,b);
return 0;
}
在實驗1中,b = a++; 相當于如下兩條語句:b = a; 和a=a+1; 即先將a的初始值8賦值給b,此時b的值即為8,然后a自增1。執(zhí)行結果是:a=9,b=8。
在實驗2中,b = ++a; 相當于如下兩條語句:a = a+1; 和 b=a; 即a先做自增1操作,然后再將a自增后的值9賦值給b,此時b的值即為9。執(zhí)行結果是:a=9,b=9。
也就是說對于自增符號++在變量后面的操作 a++,是先做其他運算,再對a進行自增運算;而自增符號++在變量前面的操作++a,是先對a進行自增運算,再做其他運算[2]。
上面這兩種自增操作,是自增操作的最基礎版本,而在很多教學資料中會出現(xiàn)類似于求 (i++)+(i++)+(i++) 、++i * ++i / i++ ; 、 (++i)+(++i)+(++i) 等等計算結果的題目分析,由于絕大多數(shù)資料中分析的方法不同,且都從語法的角度來進行解析,而非從編譯器的操作流程入手,從而給出的解釋各異,本文就針對兩個不同的編譯平臺對這段程序進行分析。
在當前的C語言教學中,教師們最常用的編譯器是微軟公司出品的VC6.0,VS2012或者更新的版本,而由于編譯器內部對C語言的語法解析有些不同,導致上例:當i=1的時候,(++i)+(++i)+(++i) 在不同的編譯器上得到的結果完全不同,本文針對這個式子,在VC6.0 和 VS2012 兩個平臺上的具體計算過程做一個對比。
用來做分析的式子為:(++i)+(++i)+(++i),寫一個簡單的示例程序以使其能在上述兩個編譯器中能運行:
int main() {endprint
int i = 1;
int result = (++i)+(++i)+(++i);
printf(“result=%d\n”,result);
return 0;
}
運行結果:
在兩款編譯器中計算得到的結果分別是10和12。
接下來在兩款編譯器中分別啟動單步調試,然后右鍵選擇查看反匯編,我們能看到每一條語句在匯編中的執(zhí)行順序是如何的:
首先看示例程序在VC6.0中的解析過程,我們重點看 int result=(++i)+(++i)+(++i);這條語句下面的反匯編代碼:
通過查看上面的反匯編代碼,可以感受到一個普通的C語言語句:int result=(++i)+(++i)+(++i);在具體的運算過程中會執(zhí)行多少操作,凝練了多少步驟,如果是純粹的機器語言,步驟還會更多,從此可見C語言如此精煉。
言歸正傳,我們通過對上面反匯編代碼的分析,能看出來編譯器是將自增操作執(zhí)行兩次之后(變量i從1變到3),再對這兩個數(shù)進行了相加運算,得到結果為6,之后再對變量i做了一次自增操作(變量i從3變到4),再同之前的結果6進行了相加運算,結果為10。
其中[ebp-4] 代表的是變量i,eax、ecx、edx是寄存器,運算過程簡析:
1:將變量i的值存到寄存器eax中,此時寄存器eax值為1。
2:將寄存器eax中的值加1存到寄存器eax中,此時寄存器eax值為2 。
3:將寄存器eax中的值傳遞給變量i,此時變量i的值是2。
4:將變量i的值存放到寄存器ecx中,寄存器ecx此時為2。
5:將寄存器ecx中的值加1存到寄存器ecx中,此時寄存器ecx值為3。
6:將寄存器ecx中的值傳遞給變量i,此時變量i的值是3。
7:將變量i的值存到寄存器edx中,此時寄存器edx值為3。
8:將變量i的值同寄存器edx中的值相加,存放到寄存器edx中,此時寄存器edx值為6。
9:將變量i的值存到寄存器寄存器eax中,此時寄存器eax值為3。
10:將寄存器eax中的值加1存到寄存器eax中,此時寄存器eax值為4。
11:將寄存器eax中的值傳遞給變量i,此時變量i的值是4。
12:將變量i的值同寄存器edx中的值相加存放到寄存器edx中,此時寄存器edx中的值是10。
13:將寄存器 edx中的值傳遞到變量i中,變量i的值為10,最后再賦值給變量result,變量result的值就是10 。
接下來我們看VS2012中是如何解析的:
在int result=(++i)+(++i)+(++i);這條語句下面的反匯編代碼如下:
通過上面反匯編代碼的查看,能看出來編譯器是將自增操作執(zhí)行三次之后(變量i從1變到4),再對這三個數(shù)進行了相加運算,得到結果為12。
[i]代表的是變量i(同VC6.0中的[ebp–4]是相同的含義),eax、ecx、edx是寄存器,運算過程簡析:
1:將變量i的值存到寄存器eax中,此時寄存器eax值為1。
2:將寄存器eax中的值加1存到eax中,此時寄存器eax值為2 。
3:將寄存器eax中的值傳遞給變量i,此時變量i的值是2。
4:將變量i的值存放到寄存器ecx中,寄存器ecx此時為2。
5:將寄存器ecx中的值加1存到寄存器ecx中,此時寄存器ecx值為3。
6:將寄存器ecx中的值傳遞給變量i,此時變量i的值是3。
7:將變量i的值存到寄存器edx中,此時寄存器edx值為3。
8:將寄存器edx中的值加1存到寄存器edx中,此時寄存器edx值為4。
9:將寄存器edx中的值傳遞給變量i,此時變量i的值是4。
10:將變量i的值傳遞給寄存器eax,此時寄存器eax值為4。
11:將變量i的值同寄存器eax中的值相加存放到寄存器eax中,此時寄存器eax的值是8。
12:將變量i的值同寄存器eax中的值相加存放到寄存器edx中,此時寄存器eax中的值是12。
13:將寄存器eax中的值傳遞到變量result中,變量result的值就是12。
2 結語
通過前面兩個實驗,我們能看出來,不同版本的編譯器,在匯編的過程對程序語法做了不同的解析,這個是我們編程人員無法控制的,并且C語言最主要的應用領域就是硬件程序開發(fā)、驅動程序開發(fā)、編解碼、算法優(yōu)化方向的開發(fā),在這些開發(fā)中,由于程序邏輯相對復雜,如果我們在編寫程序的過程中過分簡化程序行數(shù),使用優(yōu)先級不明確或者是不同編譯器不明確的運算方法,會導致很嚴重的錯誤,類似于上例的程序,在程序出現(xiàn)異常的情況下,開發(fā)人員往往很難定位錯誤出現(xiàn)的位置以及原因,會極大的浪費時間,作為教師應該要求學生養(yǎng)成良好的編程習慣,用科學、嚴謹?shù)膽B(tài)度對待遇到的問題,在熟練掌握基規(guī)律之后,編寫程序之時,有選擇地小心謹慎地使用自增(自減)運算符來簡化程序,在易錯的地方可用其他方法來代替,從而保證程序的執(zhí)行萬無一失[3]。
參考文獻
[1] 張秀建.試析C語言中的自增自減運算符[J].電腦編程技巧與維護,2017(11):22-24,64.
[2] 闞鈿玉.C語言中自增(自減)運算符號的應用于分析[J].現(xiàn)代計算機,2016(15):40-43.
[3] 唐婷,呂浩音.C語言自增(自減)運算符運算規(guī)律的探討[J].隴東學院學報,2016(5):8-11.endprint