張合花, 齊永奇
(1.鄭州大學 物理工程學院, 鄭州 450001; 2.華北水利水電大學 機械學院, 鄭州 450045)
減少后置自增自減運算符非預期結果的研究
張合花1, 齊永奇2
(1.鄭州大學 物理工程學院, 鄭州 450001; 2.華北水利水電大學 機械學院, 鄭州 450045)
摘要:在VC 6.0中調(diào)用函數(shù)時,如果實際參數(shù)表達式中使用了后置自增、自減運算符,發(fā)布版的運行結果可能不同于調(diào)試版的預期。本文在分析問題構成條件的基礎上,選擇編寫Add-in類型的程序來發(fā)現(xiàn)這種問題并提醒程序員修改源程序,并在討論有關注意事項后列出了主要開發(fā)步驟。所開發(fā)的Add-in程序可自動分析VC 6.0活動項目中的所有源文件和頭文件,檢查是否存在這種問題,從而降低出現(xiàn)非預期結果的可能性。
關鍵詞:自增運算符;自減運算符;調(diào)試版;發(fā)行版;Add-in;VC 6.0
由于高版本VC軟件的局限性,VC 6.0仍然是功能最強大、使用范圍最廣泛的軟件開發(fā)工具[1]。它的一個重要功能是可以在調(diào)試版中單步執(zhí)行和跟蹤,排除算法錯誤后再生成小而快的發(fā)布版可執(zhí)行文件[2]。目前,許多高校用它作為C/C++的教學平臺,程序員也用它進行各種各樣的應用開發(fā)。C/C++的自增、自減運算符具有簡便、高效的特點,常被用來提高程序的運行速度。
使用自增、自減運算符的主要問題在于當它被用于復雜表達式的時候會降低可移植性。這是因為標準C/C++沒有規(guī)定子表達式的求值順序,而允許各編譯系統(tǒng)自己安排[3-5]。我們在實踐中發(fā)現(xiàn),在VC 6.0中調(diào)用函數(shù)時,如果實際參數(shù)表達式中使用了后置自增、自減運算符,并且受該運算符作用的對象在多個實際參數(shù)表達式中同時出現(xiàn),那么發(fā)布版的運行結果可能與調(diào)試程序時的預期結果不相同。為此,本文用VC 6.0開發(fā)了一個能夠?qū)υ闯绦蜻M行自動分析的Add-in程序。該程序一旦發(fā)現(xiàn)這種情況,就會提醒程序員修改源程序。
1問題及構成條件
1.1問題
我們用VC 6.0新建了一個控制臺類型的項目,項目名為PostfixError。其中有一個名字為Postfix.cpp的源文件,文件內(nèi)容如下:
#include
void func(int i,int j){
cout<<“i =”<
void main(){ int a = 1; func(a,a--); }
在調(diào)試版下運行程序,輸出為:
i=1,j=1
而在發(fā)布版下,輸出為:
i=0,j=1
顯然二者存在差別,但是在兩個版本下進行編譯、鏈接時均沒有任何錯誤或警告。將自減運算符改為自增運算符后,問題同樣存在。
程序員往往在調(diào)試版中調(diào)試算法,如果運行結果符合預期,則認為算法正確,發(fā)布程序時往往不再全面測試運行結果。而且對于復雜的源程序來說,即使是調(diào)試版,也很難進行全面的測試。這就可能產(chǎn)生錯誤的程序運行結果。
1.2問題的構成條件
實踐發(fā)現(xiàn),在VC 6.0中使用自增、自減運算符時,調(diào)試版與發(fā)布版的運行結果大多數(shù)情況下相同,僅當同時滿足以下條件時才可能出現(xiàn)不同:①自增或自減運算符出現(xiàn)在函數(shù)調(diào)用的實際參數(shù)表達式中;②該運算符為后置的而非前置的;③所調(diào)用的函數(shù)具有至少2個形式參數(shù),并且為全局函數(shù)而非成員函數(shù);④該運算符的作用對象至少在所調(diào)用函數(shù)的2個實際參數(shù)表達式中同時出現(xiàn)。
2解決方案及有關事項
2.1用Add-in查找問題
文獻[6]列舉、比較了3 條可以用來與VC 6.0交互的途徑,并指出編寫Add-in類型的程序是與VC 6.0交互的最好選擇。在Add-in程序中可以對VC 6.0活動項目中的所有源文件和頭文件進行分析,查找各種各樣的問題。它已經(jīng)被成功地用來減少重復聲明復合類型可能帶來的風險[6]以及使用goto語句可能出現(xiàn)的死循環(huán)[7]。因此,我們可選擇編寫Add-in類型的程序來查找符合上述條件的情況,發(fā)現(xiàn)問題時輸出警告信息。
2.2確定源程序分析時機
Add-in程序以動態(tài)鏈接庫的形式存在,無法在Windows中直接運行,卻可以在VC 6.0中加載并運行。VC 6.0根據(jù)已經(jīng)發(fā)生或即將發(fā)生的事件調(diào)用Add-in程序中的不同函數(shù),實現(xiàn)與Add-in程序的交互。這些事件包括打開或者關閉文檔、即將執(zhí)行Build菜單命令或執(zhí)行完畢Build菜單命令等。因此,Add-in程序可選擇不同的時機對源程序進行分析。但是文件分析非常復雜,為了避免諸如語法錯誤等造成的分析困難,一般選擇在源程序無編譯和鏈接錯誤時對其進行分析。
為了僅處理屬于源程序的文件,并且不產(chǎn)生遺漏,還應該在分析之前由Add-in程序通知VC 6.0,讓其保存程序員針對項目所做的改動,包括添加和刪除文件等。這是因為默認情況下執(zhí)行Build菜單命令時僅自動保存針對文件內(nèi)容所做的改動。
2.3準確查找和定位
(1)發(fā)現(xiàn)問題時給出的警告信息應該完整和準確。因為同一個函數(shù)可能在多處使用不同形式的實際參數(shù)調(diào)用,所以當發(fā)現(xiàn)問題時僅給出函數(shù)調(diào)用表達式及其所在文件的名字是不夠的,還必須指出問題出現(xiàn)在哪一行以及哪個或者哪些變量存在問題,才有助于程序員迅速解決問題。
(2)分析文件時需要排除那些不滿足條件的情況,以便盡量避免誤報。我們很容易判定變量情況是否滿足問題構成條件①③④,但是很難判定是否滿足條件②。這是因為條件②的判定依據(jù)是該運算符的左側存在作用對象,并且該作用對象為左值表達式,而左值表達式的提取與判定非常困難。因此,目前僅限于處理變量(包括數(shù)組元素,下同),即只要該運算符左側為變量,就認為它是后置的。
(3)分析文件時需要避免字符串或注釋中存在符合條件的內(nèi)容而產(chǎn)生誤報,同時避免將其他內(nèi)容當作字符串或注釋而產(chǎn)生漏報。這可以通過簡化字符串和注釋來實現(xiàn)。但是,簡化只能在內(nèi)存中進行,不能改變原內(nèi)容。為了方便簡化字符串和注釋,通常還需簡化字符常量。在所有這些處理中,均不能改變行序號,以免在給出行序號時出錯。
3實現(xiàn)解決方案的主要步驟
由于Add-in程序的代碼較長,這里僅給出主要開發(fā)步驟及實現(xiàn)的功能。
3.1創(chuàng)建Add-in項目
在VC 6.0中執(zhí)行File/New菜單命令,彈出對話框。在Projects選項卡的列表框中選擇DevStudio Add-in Wizard類型,在項目名編輯框中輸入項目名FindPostfixError。點擊OK按鈕,再彈出對話框。取消復選框Provides a toolbar的選中狀態(tài),使其只能自動運行,以免有錯誤時被人工運行。確保復選框Responds to Developer Studio events處于選中狀態(tài),使其響應VC 6.0的事件,以便在正確的時機自動運行。點擊Finish按鈕,再點擊OK按鈕,完成Add-in項目的創(chuàng)建。
3.2保存項目改動
在工作區(qū)的ClassView面板上展開樹狀類視圖,可以找到類中類XApplicationEvents的成員函數(shù)BuildFinish()。在該函數(shù)中,當其形式參數(shù)nNumErrors為0時對源程序中的文件進行分析,才符合前述分析時機的要求。這是因為VC 6.0執(zhí)行Build菜單命令之后才自動調(diào)用該函數(shù),而nNumErrors的值為0說明沒有編譯、鏈接錯誤。
在分析文件之前通過數(shù)據(jù)成員m_pCommands調(diào)用成員函數(shù)GetApplicationObject(),獲得指向VC 6.0的Application對象指針。通過該指針調(diào)用成員函數(shù)ExecuteCommand()來執(zhí)行WorkspaceSave命令,讓VC 6.0保存程序員針對項目的改動。
3.3簡化文件內(nèi)容
通過上述Application對象指針調(diào)用成員函數(shù)get_ActiveProject(),獲得指向活動項目的指針。通過活動項目指針調(diào)用成員函數(shù)get_FullName(),獲得擴展名為dsp的項目信息文件的全名。根據(jù)此文件的內(nèi)容獲得活動項目中所有文件的全名。根據(jù)文件的擴展名篩選出源文件和頭文件,將其內(nèi)容讀入內(nèi)存,并在內(nèi)存中進行簡化。
(1)將最前面的字符設為當前字符。
(2)如果該字符為單引號則轉(zhuǎn)到(3),如果該字符為雙引號則轉(zhuǎn)到(4),如果該字符及其后字符均為斜杠則轉(zhuǎn)到(5),如果該字符為斜杠且其后字符為星號則轉(zhuǎn)到(6)。除此之外,將下一字符設為當前字符繼續(xù)進行判斷,直到處理完所有字符。
(3)說明正在處理的字符常量,向后搜索與其配對的單引號,并將它們之間的內(nèi)容刪除。之后將配對的單引號后面的字符設為當前字符并返回(2)。
(4)說明正在處理的字符串常量,向后搜索與其配對的雙引號,并將它們之間的內(nèi)容刪除。之后將配對的雙引號后面的字符設為當前字符并返回(2)。
(5)說明正在處理的以雙斜杠作為起始標記的注釋,刪除雙斜杠及其后被注釋掉的內(nèi)容。之后將注釋內(nèi)容結束位置所在行的換行符設為當前字符并返回(2)。
(6)說明正在處理的以/*作為起始標記和以*/作為結束標記的注釋塊,刪除/*和*/以及它們之間的內(nèi)容。之后將注釋結束標記之后的字符設為當前字符并返回(2)。
需要說明的是,以上處理中皆不能刪除表示文件內(nèi)容換行的換行符,而字符常量、字符串常量和兩種形式的注釋都可能跨行。
3.4查找問題
對經(jīng)過上述簡化處理的內(nèi)存進行處理,查找可能存在問題的自增、自減運算符。
(1)將最前面的字符設為起始位置。
(2)從起始位置向后搜索自增、自減運算符。如果找到了,就記錄其位置并轉(zhuǎn)到(3),否則結束。
(3)從該運算符的左側字符向前搜索未配對的左圓括號,遇到分號或花括號則停止。如果找到了,就記錄其位置并轉(zhuǎn)到(4),否則說明它所處的表達式不是函數(shù)調(diào)用表達式,需轉(zhuǎn)到(8)。
(4)提取左圓括號至該運算符左側字符之間的內(nèi)容,并在提取內(nèi)容中從后向前提取變量名。如果提取成功,說明是后置的,則記錄變量名并轉(zhuǎn)到(5),否則需轉(zhuǎn)到(8)。
(5)從該運算符的右側字符向后搜索未配對的右圓括號,遇到分號或花括號則停止。如果找到了,就記錄其位置并轉(zhuǎn)到(6),否則說明它所處的表達式不是函數(shù)調(diào)用表達式,則轉(zhuǎn)到(8)。
(6)提取上述左圓括號至右圓括號之間的內(nèi)容,然后在提取的內(nèi)容中搜索(4)中記錄的變量名并記錄每次出現(xiàn)的位置。搜索時需要注意它不能是另一個標識符的一部分。如果變量名出現(xiàn)次數(shù)小于2,則轉(zhuǎn)到(8),否則在任意兩相鄰位置之間搜索逗號。如果找到了,則轉(zhuǎn)到(7),否則說明該變量沒有在多個實際參數(shù)表達式中同時出現(xiàn),轉(zhuǎn)到(8)。
(7)在左圓括號之前提取函數(shù)名。如果提取失敗,說明這一對圓括號不是函數(shù)調(diào)用運算符,需轉(zhuǎn)到(8),否則檢查函數(shù)名前面是否為點運算符或箭頭運算符。如果是,則說明調(diào)用的函數(shù)為成員函數(shù),也轉(zhuǎn)到(8),否則提取完整的函數(shù)調(diào)用表達式并將其記錄到鏈表,同時記錄該函數(shù)名所在行的序號、(4)中記錄的變量名及所處理文件的名字。
(8)將自增或自減運算符的右側字符設為起始位置,然后返回(2)。
需要說明的是,以上所有搜索都必須在有效范圍內(nèi)進行,搜索自增或自減運算符、提取變量名或函數(shù)名時皆應考慮可能存在續(xù)行問題。
3.5輸出警告信息
如果經(jīng)過上述處理得到的鏈表不為空,則需要先檢查有沒有相同項。如果有就合并,以免重復報告。然后,以對話框的形式發(fā)出警告,提醒程序員注意。最后,還要清除鏈表,以便在下次執(zhí)行Build菜單命令時不受以前的影響。
4Add-in的運行
對于Add-in 類型的FindPostfixError項目,執(zhí)行Build菜單命令,生成動態(tài)鏈接庫文件。然后,執(zhí)行Tools/Customize菜單命令,在Add-ins and Macro Files選項卡上點擊Browse按鈕找到該文件,VC 6.0將自動地注冊并啟用它,讓其響應VC 6.0的事件。因此,一旦完成Add-in程序的開發(fā),就可以將其應用到任何安裝了VC 6.0的計算機上,幫助程序員降低程序出錯的可能性。
打開控制臺類型的PostfixError項目,執(zhí)行Build菜單命令,將彈出圖1所示的對話框。這說明所編寫的Add-in程序發(fā)現(xiàn)了PostfixError項目中存在著發(fā)布版運行結果不同于調(diào)試版的可能性,并給出了詳細信息,包括文件名、函數(shù)調(diào)用表達式、問題變量及其所在行的序號等。
圖1 發(fā)現(xiàn)問題時彈出的對話框
運行結果表明,Add-in程序在多數(shù)情況下,能夠發(fā)現(xiàn)使用后置自增、自減運算符時可能存在問題的函數(shù)調(diào)用表達式。
5結語
所開發(fā)的Add-in程序能夠自動地尋找在VC 6.0中使用自增、自減運算符時有可能出現(xiàn)非預期結果的地方,有助于及時發(fā)現(xiàn)問題,避免造成不良后果。因此,它可以為廣大學生和工程技術人員學習、掌握和運用VC 6.0,進行程序設計提供幫助。在實踐中,需要設法避免自增、自減運算符左側的作用對象為變量及數(shù)組元素之外的左值表達式時出現(xiàn)的漏報現(xiàn)象。
參考文獻:
[1]王育堅.Visual C++面向?qū)ο缶幊探坛蘙M].北京:清華大學出版社,2003.
[2]王華.VC++ 6.0 Release和Debug的不同[J].科技信息,2010(11):475.
[3]譚浩強.C程序設計(第四版)[M].北京:清華大學出版社,2010:366-367.
[4]邢莉莉.解析C語言中自增運算符問題[J].科技風,2009(20):8.
[5]黃玉蘭.有關C語言輸出函數(shù)中的自增自減運算符在不同編譯環(huán)境中的探討[J].科技致富向?qū)?2013(20):26-27.
[6]張全法,齊永奇.用Add-in提高VC 6.0集成開發(fā)環(huán)境的查錯能力研究[J].中原工學院學報,2009,20(3):47-50.
[7]張全法,陳倩.用Add-in減少VC 6.0中goto語句使用錯誤的研究[J].中原工學院學報,2013,24(2):57-60.
(責任編輯:王長通)
Study on Reducing Unexpected Result of Postfix
Increment or Decrement Operator
ZHANG He-hua1, QI Yong-qi2
(1. Zhengzhou University, Zhengzhou 450001;
2. North China University of Water Resources and Electric Power, Zhengzhou 450045, China)
Abstract:When developing programs with VC 6.0, if postfix increment or decrement operator is used in actual argument expression of function, the running result of release configuration may be unexpectedly different from that of debug configuration. Based on analyzing the conditions under which this kind of problem will appear, chosen Add-in to find this kind of problem for notifying the programmer to modify its program, discussed some special questions in practice, and given the main steps in developing the Add-in. The Add-in can be used to analyze all source files and header files of the active project in VC 6.0 to determine if there is this kind of problems, thus the probability of producing unexpected result is reduced.
Key words:increment operator; decrement operator; debug configuration; release configuration; Add-in; VC 6.0
文章編號:1671-6906(2015)01-0091-04
作者簡介:朱登雷(1986-),男,河南安陽人,碩士生。
基金項目:國家自然科學基金項目(51077134)
收稿日期:2014-03-21
中圖分類號:TP311.52
文獻標志碼:ADOI:10.3969/j.issn.1671-6906.2015.01.022