• 
    

    
    

      99热精品在线国产_美女午夜性视频免费_国产精品国产高清国产av_av欧美777_自拍偷自拍亚洲精品老妇_亚洲熟女精品中文字幕_www日本黄色视频网_国产精品野战在线观看

      ?

      C 語(yǔ)言程序的理解與編譯優(yōu)化

      2020-08-07 14:44:38吳元斌
      現(xiàn)代計(jì)算機(jī) 2020年18期
      關(guān)鍵詞:運(yùn)算符編譯器次序

      吳元斌

      (重慶三峽學(xué)院計(jì)算機(jī)科學(xué)與工程學(xué)院,重慶404000)

      0 引言

      在多年的C 語(yǔ)言教學(xué)實(shí)踐中發(fā)現(xiàn),不少初學(xué)者對(duì)C 語(yǔ)言中運(yùn)算求值存在一些模糊認(rèn)識(shí)。很多人認(rèn)為C語(yǔ)言的算術(shù)表達(dá)式求值順序與數(shù)學(xué)中算術(shù)表達(dá)式的求值順序相同,即先乘除、后加減。C 語(yǔ)言標(biāo)準(zhǔn)規(guī)定了運(yùn)算符間的優(yōu)先級(jí)及同級(jí)運(yùn)算的結(jié)合性[1],也是乘除運(yùn)算的優(yōu)先級(jí)高于加減運(yùn)算,因此對(duì)于表達(dá)式a+b-c*d,其運(yùn)算順序看起來(lái)是:*、+、-,但事實(shí)上并非如此。

      一些習(xí)題包括一個(gè)變量多次自增(或自減)求和的表達(dá)式,如:(a++)+(a++)。C 語(yǔ)言并沒(méi)有規(guī)定這兩個(gè)自增運(yùn)算與相加的求值順序。通常的理解是:先求左邊自增的值,再求另右邊自增的值,最后將兩個(gè)值相加,但實(shí)際上,有些編譯器進(jìn)行了優(yōu)化:先進(jìn)行兩次自增,然后再將兩個(gè)a 相加。還有其它依賴于編譯器的問(wèn)題,出現(xiàn)在習(xí)題或思考題甚至考試題中。這種情況是應(yīng)該避免的,因此程序的運(yùn)行結(jié)果是依賴于編譯器的,在不同的編譯器下運(yùn)行結(jié)果可能不同。

      為了清楚的理解C 語(yǔ)言教學(xué)中存在的一些編譯相關(guān)的問(wèn)題,使初學(xué)者編寫(xiě)與不依賴于編譯器的C 語(yǔ)言程序,本文將列舉一些典型的C 語(yǔ)言示例程序,給出了它們?cè)诩砷_(kāi)發(fā)環(huán)境Eclipse + MinGW GCC、LCCWin32 以及在Visual Studio 2019 下的運(yùn)行結(jié)果對(duì)比。由于多數(shù)示例程序的運(yùn)行結(jié)果存在一些差異,進(jìn)一步展示、對(duì)照和分析了源程序在開(kāi)源編譯器MinGW GCC和LCC 下目標(biāo)程序的反匯編程序,目標(biāo)程序的反匯編程序是利用Eclipse+MinGW GCC、LCC-Win32 兩種集成開(kāi)發(fā)環(huán)境調(diào)試程序環(huán)境下得到的。本文還對(duì)編譯器翻譯算術(shù)表達(dá)式的基本思想進(jìn)行說(shuō)明,并分析編譯器在表達(dá)式運(yùn)算求值順序?qū)崿F(xiàn)中的具體差異。

      1 示例源程序及其目標(biāo)程序反匯編分析

      1.1 算術(shù)表達(dá)式中運(yùn)算的次序

      算術(shù)表達(dá)式是用二元運(yùn)算符+、-、*、/和圓括號(hào)連接起來(lái)的滿足語(yǔ)法和語(yǔ)義規(guī)則的式子,C 語(yǔ)言規(guī)定了其中運(yùn)算符的優(yōu)先級(jí)和結(jié)合性,如圓括號(hào)的優(yōu)先級(jí)最高,之后是乘除,加減最低,同一優(yōu)先級(jí)的兩個(gè)算術(shù)運(yùn)算符的結(jié)合性是從左到右,但C 語(yǔ)言算術(shù)表達(dá)式中運(yùn)算的次序不等同于數(shù)學(xué)中運(yùn)算的次序。通過(guò)分析一些C 語(yǔ)言教材[2-4]中給出的容易引起模糊認(rèn)識(shí)的示例,用下列典型程序段進(jìn)行說(shuō)明:

      照數(shù)學(xué)運(yùn)算規(guī)則,賦值語(yǔ)句右邊的表達(dá)式運(yùn)算順序依次是:*、/、*、+、-,運(yùn)算過(guò)程和結(jié)果可以表示為:a* 4 + b/ 2- c * b →12 + b/ 2- c * b →12 + 2 - c *b →12+2-20 →14-20 →-6。雖然C 語(yǔ)言程序中表達(dá)式的運(yùn)行結(jié)果值也是-6,但運(yùn)算次序與上述數(shù)學(xué)運(yùn)算次序是不同的。編譯程序是用下面的文法對(duì)算術(shù)表達(dá)式進(jìn)行了嚴(yán)格的定義,文法指明了運(yùn)算符的結(jié)合性和優(yōu)先級(jí),算術(shù)表達(dá)式的文法為[5-6]:

      其中非終結(jié)符E 表示一組以+號(hào)或-號(hào)分隔的項(xiàng)所組成的表達(dá)式;T 表示由一組以*號(hào)或/號(hào)分隔的因子所組成的項(xiàng),F(xiàn) 表示因子,它是用括號(hào)括起來(lái)的表達(dá)式或標(biāo)識(shí)符id。變量和常數(shù)被詞法分析程序歸類為標(biāo)識(shí)符id。利用該文法生成上面表達(dá)式的語(yǔ)法樹(shù)是唯一的,如圖1 所示。

      圖1 生成示例表達(dá)式的語(yǔ)法樹(shù)

      其中,圖中的id 對(duì)應(yīng)的變量或常量從左到右依次是a、4、b、2、c、b。通過(guò)語(yǔ)法制導(dǎo)翻譯(類似于語(yǔ)法樹(shù)自下而上或遞歸下降分析),上述表達(dá)式被翻譯成為每條指令最多一個(gè)運(yùn)算符的指令序列[5]。將上述表達(dá)式計(jì)算的同一示例程序在兩種集成環(huán)境中調(diào)試視圖截圖(源程序及反匯編)如圖2 所示,左圖是Eclipse+Min-GW GCC,右圖LCC-Win32,兩個(gè)子圖的上面部分都是源程序,下面都是目標(biāo)程序的反匯編程序。左、右兩圖放在一起是便于對(duì)比分析(下面示例都采用這種方式)。

      其中左圖中最后用紅色邊框包括的列為手工添加的代碼說(shuō)明,右圖的反匯編用圓括號(hào)包含了變量名。兩種編譯器采用的指令不完全相同,左圖中除法比右圖的實(shí)現(xiàn)復(fù)雜,詳細(xì)分析可見(jiàn)文獻(xiàn)[7]??梢钥闯?,兩個(gè)編譯器處理表達(dá)式的運(yùn)算次序相同的,它們的次序都是:*、/、+、*、-,顯然與數(shù)學(xué)運(yùn)算規(guī)則不同。這個(gè)運(yùn)算次序也就是圖1 中的語(yǔ)法樹(shù)按運(yùn)算符從下到上、從左到右得到的運(yùn)算次序。

      實(shí)際上,C 語(yǔ)言算術(shù)表達(dá)式求值中在處理當(dāng)前運(yùn)算符時(shí),要與其右邊相鄰的運(yùn)算符進(jìn)行比較,若當(dāng)前運(yùn)算符高于其相鄰右邊的運(yùn)算符,或者它們的優(yōu)先級(jí)相同,且結(jié)合性是從左到右,則完成當(dāng)前運(yùn)算,否則先處理其右邊相鄰的運(yùn)算,其右邊相鄰的運(yùn)算也要處理方法也是相同的,如此等等,直到所有運(yùn)算完成。編譯器自下而上的翻譯法在表達(dá)式最后添加了一個(gè)運(yùn)算符$(優(yōu)先級(jí)最低)運(yùn)算符[5-6],它就沒(méi)有右邊相鄰的運(yùn)算符了。

      圖2 兩種編譯器表達(dá)式運(yùn)算次序相同

      1.2 表達(dá)式運(yùn)算次序的副作用

      很多時(shí)候無(wú)論是按數(shù)學(xué)運(yùn)算次序還是按編譯程序指定的次序運(yùn)算,算術(shù)表達(dá)式的值是相同的,看起來(lái)不用區(qū)分C 語(yǔ)言表達(dá)式的運(yùn)算次序,但并不總是這樣。當(dāng)表達(dá)式中存在項(xiàng)與項(xiàng)之間的值存在依賴關(guān)系時(shí),結(jié)果就可能不同,如將上面的程序段修改為:

      所作的修改只是將上例的表達(dá)式最后一項(xiàng)改為(b=2),由于第2 項(xiàng)中的b 與最后一項(xiàng)的b 存在依賴關(guān)系,編譯器MinGW GCC 與LCC 對(duì)表達(dá)式的優(yōu)化處理不同,程序在兩種環(huán)境下的運(yùn)行結(jié)果不同,分別為4和3,在Visual Studio 2019 下運(yùn)行的結(jié)果也為3。

      該程序在MinGW GCC 與LCC 編譯反匯編對(duì)比如圖3 所示,分析發(fā)現(xiàn)LCC 編譯器中b 賦值為2 的指令被提前,MinGW GCC 則沒(méi)有,兩條指令分別用紅色方框標(biāo)出,表達(dá)式中其它的運(yùn)算次序還是與上例相同??梢?jiàn),兩種編譯器只是對(duì)“(b=2)”的處理不同,在LCC環(huán)境下表達(dá)式中所有b 的值都是2,而在MinGW GCC環(huán)境下只是最后的b 的值為2,因此導(dǎo)致了程序的運(yùn)行結(jié)果不同,所以該程序的運(yùn)行結(jié)果完全依賴于編譯環(huán)境。

      圖3 兩種編譯器對(duì)運(yùn)算次序優(yōu)化存在差異

      1.3 函數(shù)實(shí)參的求值順序處理

      C 語(yǔ)言也沒(méi)有指定函數(shù)各參數(shù)的求值順序[1]。在函數(shù)調(diào)用時(shí),有的編譯器是從左到右,有的則是從右到左。參考文獻(xiàn)[3]有一個(gè)類似于下面的程序段:

      通過(guò)在MinGW GCC 與LCC 下運(yùn)行程序后發(fā)現(xiàn),第1 個(gè)輸出語(yǔ)句的運(yùn)行結(jié)果不同,分別為“3 3”和“3 0”,而第1 個(gè)輸出語(yǔ)句在Visual Studio 2019 下運(yùn)行的結(jié)果為“3 3”。三種環(huán)境下第2 個(gè)輸出語(yǔ)句的結(jié)果為“3 3 3”,即變量a、b、c 的值最后都是3,這說(shuō)明在三種環(huán)境下編譯器的求值順序都是從右到左。由于第1 個(gè)輸出存在差異,為了找出問(wèn)題的原因,將該程序在Min-GW GCC 與LCC 編譯反匯編進(jìn)行對(duì)比,如圖4 所示。

      圖4 兩種編譯器對(duì)實(shí)參求值的處理不同

      分析圖4 發(fā)現(xiàn),兩種編譯器都是先計(jì)算右邊的參數(shù)(左圖中第1 個(gè)加框的部分為第1 個(gè)參數(shù)的求值,右圖中為第1 個(gè)加框指令的前6 條指令),然后計(jì)算左邊的參數(shù)。但是它們處理值的方式不同,對(duì)MinGW GCC環(huán)境中,printf 輸出的是變量a 的最終值(左圖第2 個(gè)加框部分)。但在LCC 環(huán)境中是將兩個(gè)實(shí)參的值分別保存在寄存器rbx、rdi 中,計(jì)算一個(gè)保存一個(gè)(右圖中兩個(gè)加框指令),rbx 的值為0,rdi 的值為3。可見(jiàn)盡管都是從右到左計(jì)算函數(shù)實(shí)參,但形參的值卻不同,程序的執(zhí)行結(jié)果也依賴于編譯程序,只有仔細(xì)分析它們的反匯編程序,才能搞清楚其中的原因。

      1.4 自增自減運(yùn)算的副作用

      函數(shù)調(diào)用、嵌套賦值語(yǔ)句、自增與自減運(yùn)算符都有可能產(chǎn)生“副作用”—在對(duì)表達(dá)式求值的同時(shí),修改了某些變量的值[1]。一些C 語(yǔ)言教材的練習(xí)中常常包含類似于下面的程序段:

      上述程序在MinGW GCC 和LCC 下下的運(yùn)行結(jié)果分別為“11 14 7 7”和“11 13 7 7”,在Visual Studio 2019下的運(yùn)行結(jié)果為“11 14 7 7”??梢?jiàn)輸出結(jié)果存在差異,為了找出問(wèn)題的原因,將該程序在MinGW GCC 與LCC編譯反匯編進(jìn)行對(duì)比,如圖5 所示。

      圖5 兩種編譯器對(duì)自增運(yùn)算處理不同

      在圖5 中,分別對(duì)兩種環(huán)境下相關(guān)指令功能進(jìn)行了說(shuō)明(紅色框線中)??梢钥闯?,兩種環(huán)境下對(duì)后綴的自增運(yùn)算處理相同,即先累加,再自增,再累加,再自增,累加的結(jié)果都是11。但對(duì)于前綴的自增運(yùn)算處理則不同,在MinGW GCC 下前綴的自增運(yùn)算被優(yōu)化(在加法運(yùn)算前進(jìn)行),而在LCC 下前綴的自增運(yùn)算則不同,并沒(méi)有優(yōu)化,所以前綴的自增運(yùn)算相加的結(jié)果不同,分別為14 和13。因此一個(gè)變量多次自增(或自減)求和表達(dá)式的值也依賴于編譯器。

      2 結(jié)語(yǔ)

      本文討論了C 語(yǔ)言教學(xué)中一些容易引起初學(xué)者產(chǎn)生模糊認(rèn)識(shí)的典型問(wèn)題,通過(guò)不同編譯環(huán)境對(duì)目標(biāo)程序的反匯編對(duì)照與分析,能夠清楚地看到這些程序的運(yùn)行結(jié)果依賴于編譯器,不同編譯器可能產(chǎn)生不同的結(jié)果。這樣的問(wèn)題還很多,雖然初學(xué)者也不一定能夠理解這些分析及編譯原理的具體細(xì)節(jié),但教學(xué)中應(yīng)該讓他們知道編寫(xiě)依賴于編譯器的程序是不好的習(xí)慣。在任何一種編程語(yǔ)言中,如果代碼的執(zhí)行結(jié)果與求值順序有關(guān),則都是不好的程序設(shè)計(jì)風(fēng)格。很自然,有必要了解哪些問(wèn)題需要避免,但是,如果不知道這些問(wèn)題在各種機(jī)器上是如何解決的,就最好不要嘗試運(yùn)用某種特殊的實(shí)現(xiàn)方式[1]。因此,C 語(yǔ)言教學(xué)一個(gè)很重要的工作是讓學(xué)生學(xué)會(huì)正確的編程方法,培養(yǎng)良好的編程習(xí)慣[2],避免編寫(xiě)執(zhí)行結(jié)果依賴于編譯器的C 語(yǔ)言程序。

      猜你喜歡
      運(yùn)算符編譯器次序
      《漢紀(jì)》對(duì)漢帝功業(yè)次序的重構(gòu)及其意義
      老祖?zhèn)魇诨具\(yùn)算符
      基于相異編譯器的安全計(jì)算機(jī)平臺(tái)交叉編譯環(huán)境設(shè)計(jì)
      生日謎題
      放假一年
      C++運(yùn)算符重載剖析
      通用NC代碼編譯器的設(shè)計(jì)與實(shí)現(xiàn)
      淺談交換積分次序
      河南科技(2013年18期)2013-11-07 07:47:14
      表達(dá)式求值及符號(hào)推導(dǎo)
      C++中運(yùn)算符的重載應(yīng)用
      星子县| 丰县| 营口市| 怀集县| 富宁县| 曲阳县| 曲沃县| 永和县| 富顺县| 凤凰县| 彭泽县| 祁连县| 连江县| 克拉玛依市| 休宁县| 丹阳市| 汶上县| 休宁县| 雅安市| 资源县| 旅游| 德庆县| 上蔡县| 莫力| 临沂市| 天峨县| 凭祥市| 张掖市| 卓资县| 化州市| 舒城县| 巴东县| 治县。| 南充市| 电白县| 麦盖提县| 岳普湖县| 汉寿县| 新营市| 洪泽县| 莫力|