演算法合集之《論程式的除錯技巧》

2022-05-07 19:12:02 字數 4703 閱讀 6297

論程式的除錯技巧

【關鍵字】除錯技巧、測試方法、測試用例設計

【摘要】本文結合作者自身經驗,對競賽中程式的除錯技巧做了詳細的闡述和總結。在介紹了程式設計中常見的錯誤型別和整合環境的除錯工具之後,給出了一般除錯流程,並著重講述了其中的動態查錯技巧,做了一定的歸納。最後通過乙個除錯例項來體現本文所論述的除錯技巧的具體應用

【正文】

一、 程式除錯的必要性

程式設計過程中,錯誤是在所難免的。雖然有些程式設計師認為乙個程式可以做到完美無瑕,但實際情況卻並非如此,不然就不會有人對windows怨氣沖天了。儘管資訊學競賽中所編的程式從來不會像windows那樣龐大,最多也是僅僅幾百k而已,但由於時間有限,選手們的程式難免有疏漏之處。

因此,除錯就成了極其重要的一環。如何在緊迫的時間內快速準確地發現並改正錯誤,正是本文所要討論的問題。

二、 常見錯誤型別歸納

《孫子兵法》雲:「知己知彼,百戰不殆。」對於程式除錯者來說,程式中的錯誤就好比是敵人,如能準確把握敵人的情況,無疑是極為有利的。

下面我們就來對常見的一些錯誤型別進行歸納並給出解決方法。

1、 思路錯誤

這要看是基本演算法錯誤還是功能缺陷。前者需要重寫大部分**,是否重寫則根據時間是否充裕而定,後者只需增加一部分**,再修改某些地方,這時應全面考慮,以防遺漏應該修改的地方。

2、 語法錯誤

這個沒什麼可說的,作為一名資訊學競賽的選手,應該對自己選擇的程式語言的語法瞭如指掌,具體在這裡就不多講了。

3、 書寫錯誤

這種錯誤令人十分頭痛,一般的書寫錯誤在編譯時都能找出來,但如果你在表示式中用到變數j時誤寫成了i,不但編譯程式找不出來,自己找時也由於兩者樣子比較相似,難以發現。排除這種錯誤只能靠「細心」兩字,具體可使用下面要介紹的靜態查錯法。

4、 輸出格式錯誤

由於現在資訊學競賽採用黑箱測試法,由於輸出格式錯誤而導致失分的例子屢見不鮮。乙個標點,乙個空格,都會導致最後的悔恨。因此,在除錯時先要核對輸出格式,針對不同輸出格式多設計幾個測試用例,以防一失足成千古恨。

5、 其它程式設計時易犯的錯誤

除了上面所說的錯誤型別外,其它就屬於程式設計時在細節上考慮不周所造成的了。下面僅列舉其中一些較為隱蔽的錯誤。只有靠平時不斷總結積累,才能真正的做到「知己知彼」。

①變數未賦初值

看下面的程式段

for i:=1 to n do

if a[i]>max then max:=a[i];

writeln(max);

這個程式段的原意顯然是要輸出陣列a中最大的數。但由於它遺漏了將max賦初值的語句,因此很可能會出現輸出的數並不在陣列a中的錯誤。應該在過程開頭添上一句max:

=-maxint;。養成變數使用前先賦初值的習慣能預防許多較隱蔽的錯誤。

2 中間運算越界

看下面這兩句語句

a:=1000;

b:=a*a div 100;

其中a,b都是integer型別。按照我們的想法,1000*1000 div 100=10000。然而當我們察看b的值的時候,卻發現b等於169。

原因是pascal在進行編譯時,總是先計算出a*a,把它放到乙個中間變數中,然後再計算出最後結果放入b中。而a*a超出了integer的範圍,這就是造成錯誤的根本原因。要使pascal能報告這類錯誤,只要開啟編譯開關q即可。

對此類錯誤解決方法是使用強制型別轉換,寫成b:=longint(a)*a div 100。編譯程式會自動把中間變數規定為longint型別,就不會越界了。

3 區域性變數與全域性變數同名造成概念混亂

這個實際上不能算錯誤,然而有許多錯誤正是因此而起。乙個常見的錯誤是當我們在過程中使用全域性變數時,忘記了自己在該過程中還定義了乙個同名的區域性變數,從而使得實際的程式與我們的思路不一致;另乙個常見的錯誤是區域性變數忘記定義,在過程(函式)中實際上使用的是全域性變數,而出現錯誤往往是在這個過程(函式)之外某個需要使用該全域性變數的地方,這就增加了除錯的難度。因此,應該盡量避免使用同名變數。

4 if-then-else語句混亂

pascal對if-then-else語句的規定是:if-then語句可以沒有else語句與之相匹配;else語句總是匹配最近的if-then語句。這一點使得我們在使用巢狀的if-else語句時容易出錯。

如下面這個例子:

if 條件a

then if 條件b then **段b

else **段a

我們的原意是讓**段a在條件a不成立時執行,但由於else語句總是匹配最近的if-then語句,因此這個else是與if 條件b then這個語句相匹配的,也就是說**段a要滿足條件a成立且條件b不成立時才會執行,與我們原意相去甚遠。解決方法是在需要的地方加乙個空的else,就如上面的例子,要在if 條件b then語句後面加乙個空的else。

5 實數比較出錯

在比較兩個實數是否相等時,如果直接用等號,往往會造成錯誤。這是浮點運算存在誤差所造成的,解決辦法是使用兩數差的絕對值與乙個相對極小量進行比較,一般說來如果abs(a-b)<1e-8,則可認為a=b。

三、 整合環境的除錯工具

對於乙個戰士來說,對自己手中的**效能特點應該瞭如指掌。對於程式除錯者來說,除錯工具就相當於**,熟練掌握除錯工具,充分發揮它的效能,對於迅速找出錯誤,加快我們的除錯速度有著極大的幫助。下面就對整合環境提供的除錯工具做一些介紹。

除錯時主要使用的兩個選單是run和debug。run選單提供了各種程式執行方式,而debug選單提供了對變數的觀察,修改以及斷點等功能。

程式的執行方式有四種:

1、 run,執行整個程式(ctrl+f9),該方式常用在總體測試上。一般每乙個測試用例都應先用該方式執行程式,如果輸出答案正確就可以直接轉到下乙個測試用例,免去了不必要的時間。即使發現錯誤也只不過比直接進入模組除錯增加了一點點時間,是完全值得的。

2、 step over,單步執行,把整個過程(函式)視為單步一次執行(f8),該方式常用在模組除錯時期,可以通過觀察變數在模組執行前後的變化情況來確定該模組中是否存在錯誤,也可以用來跳過已測試完畢的模組。

3、 trace into,單步執行,對於過程(函式)進入到內部一步步執行(f7),該方式常用在底層除錯時期,可以跟蹤程式的每步執行過程。它的優點是容易直接定位錯誤,缺點是除錯速度較慢,尤其是當錯誤位於程式後部時。所以一般是採用先用模組除錯法盡量縮小錯誤範圍,然後使用第4種執行方式和斷點來快速跳過沒有出現錯誤的部分,最後才是用該方式來逐步跟蹤找出錯誤。

4、 go to cursor,執行到游標處(f4),之所以把這種方式放在最後介紹,是因為這種方式的靈活度較大,不但可以一次執行一行,也可以一次執行多行;可以直接跳過過程(函式),也可以進入過程(函式)內部。它有斷點的定位能力強的優點,又比斷點更加靈活。正確適當地使用這種方式可以大大加快我們除錯的速度,這需要靠豐富的除錯經驗。

可以參考後面的除錯例項。

debug選單中最常用選項是watch和add watch,這兩個用於跟蹤觀察變數和表示式在程式執行過程中值的變化,這樣就可以隨時檢查它們是否按照演算法要求輸出,是否符合正確答案。大多數錯誤在除錯時都可以只使用它們以及上面的四種執行方式被檢查出來。

有的時候,雖然知道該模組有錯,但一時無法找到錯誤所在,並且上面所講的後三種執行方式都難以快速定位。例如對於乙個程式,我們需要它執行到某個語句並滿足某個條件時停下來,go to cursor只能保證執行到這個語句時停下來,卻不能保證滿足條件,這時我們便需要使用斷點。斷點雖然沒有go to cursor靈活,但有乙個go to cursor無法取代的優勢便是它可以設定中斷條件。

使用add breakpoint選項(ctrl+f8)可以在當前游標處設定乙個斷點,breakpoints選項可以編輯斷點條件。要注意的是,斷點的設定會大大降低程式執行效率,因此除錯完畢以後一定要記得清除所有斷點。

evaluate/modify選項(ctrl+f4)用於臨時觀察修改表示式的值,如果在除錯過程中,需要臨時觀察或修改某個表示式的值,則可以開啟evaluate and modify視窗進行操作。這個方法尤其適用於對過程(函式)的除錯,我們可以先用go to cursor執行到某個過程(函式)的開頭,然後使用evaluate/modify選項改變引數的值用於檢查不同情況下這個過程(函式)的執行結果。但是要注意,乙個過程(函式)通常不是孤立的,它經常需要使用某些全域性變數,因此僅改變引數而不改變其他全域性變數的值有可能是非法的。

所以需要特別的小心,避免出現這樣的情況。

call stack選項(ctrl+f3)用於顯示當前堆疊狀態,這特別適用於對遞迴演算法的除錯。我們可以利用它察看每層引數的傳遞情況。不過一般來說引數的傳遞不易出錯,因此該選項的用處並不是很大。

四、 除錯的一般過程

對於一道題我們一般按照以下流程圖進行除錯:

除錯開始

通過靜態查錯?

y總體測試通過? y

nn模組測試通過? y 所有模組已經測試完畢? y

n跟蹤除錯

修改錯誤,重新編碼

測試用例完否?

yn除錯結束

這只是針對一般情況而言,在具體除錯時,可以改變某些步驟,比如說在發現某個模組有錯誤改正以後,可以不返回到靜態查錯階段,而是繼續該模組的查錯。這要視具體情況而定。

下面我們對各個步驟作詳細的敘述。

1、 靜態查錯

靜態查錯是指不執行程式,僅根據程式和框圖對求解過程的描述來發現錯誤。由於有些錯誤執行時很難查出,但在靜態查錯中卻容易發現,比如說前面說到的書寫錯誤。因此,靜態查錯往往是不可忽視的審查步驟。

靜態查錯一般順序為先查程式區域性,後查程式整體。查區域性主要是獨立地檢查各個子模組的功能是否按照演算法要求實現,查整體主要是檢查各個區域性之間的介面是否吻合,是否缺少某些功能。在靜態查錯階段,我們可以有針對性地檢查上面所列舉的錯誤型別。

在這個階段查出的錯誤越多,節省的除錯時間也就越多。

程式除錯技巧

1 除錯技巧 除錯之前,先要靜態查錯。1.看演算法是否正確 其實應該是在編碼之前做的工作 2.看有沒有筆誤 如i寫成j 除錯一般是分幾個步驟的。先把開關r,s,q,i開啟!1.建立測試資料 技巧見後面的文章 可以抄樣例資料 2.執行程式 3.如果結果正確,轉1 測試新的資料 或結束 4.根據結果先判...

演算法合集之《搜尋方法中的剪枝優化》

搜尋方法中的剪枝優化 南開中學齊鑫 關鍵字 搜尋 優化 剪枝 摘要 本文討論了搜尋方法中最常見的一種優化技巧 剪枝,而且主要以剪枝判斷方法的設計為核心。文章首先借助搜尋樹,形象的闡明了什麼是剪枝 然後分析了設計剪枝判斷方法的三個原則 正確 準確 高效,本文將常見的設計剪枝判斷的思路分成可行性剪枝和最...

演算法合集之《圖論的基本思想及方法》

湖南省長沙市長郡中學任愷 文章著眼於圖論基本思想及方法的討論,不涉及高深的圖論演算法。文章主要從兩方面闡述圖論的基本思想 一是合理選擇圖論模型 二是如何深入挖掘問題本質,充分利用模型的特性。同時還歸納了一些解決問題的普適性方法。基本思想 圖論模型 問題本質 定義法 分析法 綜合法 圖是用點和邊來描述...