摘 要:自增自減運(yùn)算符是C語(yǔ)言的一個(gè)特色,本文通過(guò)表達(dá)式中對(duì)一個(gè)變量進(jìn)行多次自增或自減運(yùn)算時(shí)產(chǎn)生的未定義行為進(jìn)行了詳細(xì)介紹,望幫助大家正確使用自增自減運(yùn)算符。
關(guān)鍵詞:C;自增自減運(yùn)算符;未定義行為
中圖分類(lèi)號(hào):TP312 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1674-7712 (2014) 02-0000-01
一、自增自減運(yùn)算符
C語(yǔ)言提供了兩個(gè)用于算術(shù)運(yùn)算的單目運(yùn)算符:自增(++)和自減(--)。有兩種用法:前置運(yùn)算(先增減后運(yùn)算),后置運(yùn)算(先運(yùn)算后增減)。下類(lèi)代碼段:
在不同的編譯器上運(yùn)行會(huì)有不同的結(jié)果:
一種是:21,8,一種是:22,8,一種是24,8;
很奇怪吧,一樣的表達(dá)式不同的編譯器,運(yùn)行結(jié)果會(huì)不同,雖然可以通過(guò)Debug反匯編分析出不同,那為什么會(huì)這樣?哪個(gè)是正確答案?遺憾的是這樣的題目是并沒(méi)有答案,如果真的要給一個(gè)答案,可以是未定義行為。
二、C語(yǔ)言中未定義行為(Undefined behavior)
未定義行為是一個(gè)非常微妙的話題,在C中許多貌似合理的行為實(shí)際上都有著不確定的行為,并且這通常是程序中BUG的源頭。未定義行為,就是C標(biāo)準(zhǔn)沒(méi)有對(duì)其進(jìn)行定義,程序員不能預(yù)測(cè)會(huì)發(fā)生什么事的計(jì)算機(jī)代碼,編譯器可以隨意進(jìn)行計(jì)算,簡(jiǎn)單說(shuō)就是語(yǔ)言規(guī)格在定義時(shí)為了編譯器工作的彈性和效率,會(huì)刻意不去規(guī)定某些規(guī)格,如果我們的程序依賴(lài)這些沒(méi)有規(guī)定的特性時(shí),就稱(chēng)之為未定義行為,這樣的程序語(yǔ)句在不同的編譯器上編譯會(huì)得到不同的結(jié)果。另外由“實(shí)現(xiàn)”定義的行為,這里的實(shí)現(xiàn)指的是C的不同編譯器,也是未定義行為。
C中的自增自減運(yùn)算就屬于未定義行為,因?yàn)镃沒(méi)有規(guī)定i++或者++i的加1動(dòng)作到底發(fā)生在一個(gè)語(yǔ)句的那個(gè)時(shí)刻(序列點(diǎn))執(zhí)行,所以不同編譯器在不同的序列點(diǎn)上執(zhí)行加1動(dòng)作就可能有不同的結(jié)果。盡管后綴前綴自增自減操作符++和--分別在輸出其舊值之后和輸出新值之前執(zhí)行加1減1運(yùn)算,但這里的“之后”常常被誤解。因?yàn)闆](méi)有任何保證確保后置前置自增自減會(huì)在輸出變量原值之后以及輸出新值之后會(huì)對(duì)表達(dá)式的其它部分進(jìn)行計(jì)算之前以及之后立即進(jìn)行,也不能保證變量的更新會(huì)在表達(dá)式完成之前的某個(gè)時(shí)刻進(jìn)行,這樣就導(dǎo)致結(jié)果的不同。這樣導(dǎo)致未定義行為會(huì)包含多個(gè)不確定的副作用,上述表達(dá)式是有副作用的,因?yàn)樵谛蛄悬c(diǎn)之間,變量的值不能保證行為唯一性,也就是說(shuō)第一次執(zhí)行完++i,它在內(nèi)存中的值可能改變也可能不變。
三、未定義行為的副作用
不確定副作用是指在同一個(gè)表達(dá)式中使用導(dǎo)致同一對(duì)象修改兩次或修改以后又被引用的自增,自減和賦值操作符的任何組合。C語(yǔ)言允許在一個(gè)表達(dá)式中使用一個(gè)以上的賦值類(lèi)運(yùn)算,包括賦值運(yùn)算符、自增運(yùn)算符、自減運(yùn)算符等。這種靈活性使程序簡(jiǎn)潔,但同時(shí)也會(huì)引起副作用。副作用主要表現(xiàn)在:使程序費(fèi)解,并易于發(fā)生誤解或錯(cuò)誤。除此之外,C中的任何的未定義行為都有可能(無(wú)論是編譯期還是運(yùn)行期)產(chǎn)生一些格式化你的硬盤(pán)的、做一些你完全想象不到的事情的代碼,所以不要試圖探究這些東西在你的編譯器中是如何實(shí)現(xiàn)的(這與我們C教科書(shū)上的許多練習(xí)正好相反)。另外C/C++規(guī)定,任何依賴(lài)于特定計(jì)算順序、依賴(lài)于在順序點(diǎn)之間實(shí)現(xiàn)修改效果的表達(dá)式,其結(jié)果都沒(méi)有保證,因此如果在任何完整表達(dá)式里存在對(duì)同一變量的多次引用,那么表達(dá)式里就不應(yīng)該出現(xiàn)對(duì)這一變量的副作用,否則就不能保證得到預(yù)期結(jié)果,還有這不是在某個(gè)系統(tǒng)里試一試的問(wèn)題,因?yàn)槲覀儾豢赡茉囼?yàn)所有可能的表達(dá)式組合形式以及所有可能的上下文。但這類(lèi)表達(dá)式在教材中很多,難道只是未定義行為的副作用,再來(lái)研究一下這類(lèi)表達(dá)式的合法性。
四、表達(dá)式的合法性
計(jì)算機(jī)科學(xué)家Brian W.Kernighan主編的《The C Programming Language》明確指出:自增與自減運(yùn)算符只能作用于變量,不能應(yīng)用于表達(dá)式以及整數(shù),例如++(a+b)與10++,const int N=0;N++;都是非法的。
為什么?因?yàn)檫@些表達(dá)式實(shí)際上包括了一個(gè)賦值運(yùn)算,a++等價(jià)于a=a+1;C語(yǔ)言中賦值運(yùn)算符存在左值,只能變量可以作為左值,表達(dá)式以及整數(shù)不能作為左值,因?yàn)樗鼈儧](méi)有確定的內(nèi)存地址。因此++(a+b)等價(jià)于(a+b)=(a+b)+1是不允許的,以此分析,(a++)+(a++)這個(gè)表達(dá)式中的后一項(xiàng)是非法的,表面上它只是一個(gè)a++,但是在第一個(gè)a++的作用下,a已經(jīng)變成a=a+1;因此第二個(gè)的a++則成為(a+1)++了,這是非法的。一般地,在一個(gè)表達(dá)式中,對(duì)同一個(gè)變量進(jìn)行多次自增或自減運(yùn)算都是非法的。C語(yǔ)言之父Dennis M.Ritchie還提醒,編譯器應(yīng)在這種情況下會(huì)給出警告,事實(shí)上Linux平臺(tái)下的編譯器GCC確實(shí)會(huì)對(duì)此給出警告(VC++6.0沒(méi)有):Warning:operation on‘a(chǎn)’may be undefined;
C++Primer解釋?zhuān)菏褂昧宋炊x行為的程序都是錯(cuò)誤的,即使程序能運(yùn)行也只是巧合,未定義行為源于編譯器不能檢測(cè)到的錯(cuò)誤或太麻煩以至無(wú)法檢測(cè)的錯(cuò)誤。然而,含有未定義行為的程序在某些編譯器中可以正確執(zhí)行,但并不能保證同一程序在不同編譯器中甚至在當(dāng)前編譯器的后繼版本中會(huì)繼續(xù)正確運(yùn)行,也不能保證程序在一組輸入上正確運(yùn)行并在另一組輸入上也正確運(yùn)行。總而言之,絕對(duì)不要寫(xiě)這種表達(dá)式,否則或早或晚會(huì)在某種環(huán)境中遇到麻煩。因此書(shū)寫(xiě)程序時(shí),要盡可能的保持清晰易懂,代碼除了供人閱讀以外,簡(jiǎn)單清晰的程序也不容易出錯(cuò)。
五、未定義行為的使用
既然C、C++、底層虛擬機(jī)(LLVM)IR中都有未定義行為,那么肯定有意義,現(xiàn)代計(jì)算機(jī)是以二進(jìn)制邏輯運(yùn)算為基礎(chǔ),“對(duì)于確定的輸入,其輸出是確定的”,行為必然是確定的。但是,如果不為計(jì)算機(jī)的基礎(chǔ)計(jì)算模型引入不確定性,人工智能(AI)不可能真正實(shí)現(xiàn)。另外為了使C應(yīng)用程序獲得更好的性能,可以通過(guò)優(yōu)化編譯器產(chǎn)生高性能,而未定義行為使優(yōu)化成為可能。
六、結(jié)束語(yǔ)
在教科書(shū)以及等級(jí)考試中,對(duì)一個(gè)變量在表達(dá)式中多次自增自減很常見(jiàn),本文針對(duì)表達(dá)式運(yùn)行結(jié)果不同的疑惑進(jìn)行了解答,詳細(xì)介紹了表達(dá)式中涉及到的未定義行為,希望對(duì)大家有正確的指導(dǎo)意義。
參考文獻(xiàn):
[1]譚浩強(qiáng).C語(yǔ)言程序設(shè)計(jì)[M].北京:清華大學(xué)出版社,2011.
[2]黃玉蘭.有關(guān)C語(yǔ)言輸出函數(shù)中的自增自減運(yùn)算符在不同編譯環(huán)境中的探討[J].科技致富向?qū)В?013(20).