VC調試篇

難怪不少前輩說調試是一個程序員最基本的技能,其重要性甚至超過學習一門語言。不會調試的程序員就意味着他即便會一門語言,卻不能編制出任何好的軟件。  程序員

我之前接觸的程序大可能是有比較成形的思路和方法,調試起來出的問題都比較小,最近這個是我本身慢慢摸索調試,接觸了不少新的調試方法,並查了不少前輩的總結,受益不淺,總結之前的和新的收穫以下:

VC調試篇

設置
爲了調試一個程序,首先必須使程序中包含調試信息。通常狀況下,一個從AppWizard建立的工程中包含的Debug Configuration自動包含調試信息,可是是否是Debug版本並非程序包含調試信息的決定因素,程序設計者能夠在任意的Configuration中增長調試信息,包括Release版本。
爲了增長調試信息,能夠按照下述步驟進行:express

  • 打開Project settings對話框(能夠經過快捷鍵ALT+F7打開,也能夠經過IDE菜單Project/Settings打開)
  • 選擇C/C++頁,Category中選擇general ,則出現一個Debug Info下拉列表框,可供選擇的調試信息 方式包括: 
     

命令行編程

Project settings數組

說明函數

工具

None學習

沒有調試信息測試

/Zd優化

Line Numbers Onlyui

目標文件或者可執行文件中只包含全局和導出符號以及代碼行信息,不包含符號調試信息

/Z7

C 7.0- Compatible

目標文件或者可執行文件中包含行號和全部符號調試信息,包括變量名及類型,函數及原型等

/Zi

Program Database

建立一個程序庫(PDB),包括類型信息和符號調試信息。

/ZI

Program Database for Edit and Continue

除了前面/Zi的功能外,這個選項容許對代碼進行調試過程當中的修改和繼續執行。這個選項同時使#pragma設置的優化功能無效

  • 選擇Link頁,選中複選框"Generate Debug Info",這個選項將使鏈接器把調試信息寫進可執行文件和DLL
  • 若是C/C++頁中設置了Program Database以上的選項,則Link incrementally能夠選擇。選中這個選項,將使程序能夠在上一次編譯的基礎上被編譯(即增量編譯),而沒必要每次都從頭開始編譯。

調試方法:

一、使用 Assert(原則:儘可能簡單) assert只在debug下生效,release下不會被編譯。

二、防護性的編程

三、使用Trace
四、用GetLastError來檢測返回值,經過獲得錯誤代碼來分析錯誤緣由
五、把錯誤信息記錄到文件中

 

位置斷點(Location Breakpoint 
  你們最經常使用的斷點是普通的位置斷點,在源程序的某一行按F9就設置了一個位置斷點。但對於不少問題,這種樸素的斷點做用有限。譬以下面這段代碼:

void CForDebugDlg::OnOK()

{

       for (int i = 0; i < 1000; i++)    //A

       {

              int k = i * 10 - 2; //B

              SendTo(k);          //C

              int tmp = DoSome(i); //D

              int j = i / tmp;    //E

       }

}     

     

  執行此函數,程序崩潰於E行,發現此時tmp爲0,假設tmp本不該該爲0,怎麼這個時候爲0呢?因此最好可以跟蹤這次循環時DoSome函數是如何運行的,但因爲是在循環體內,若是在E行設置斷點,可能須要按F5(GO)許屢次。這樣手要不停的按,很痛苦。使用VC6斷點修飾條件就能夠輕易解決此問題。步驟以下。 
  1 Ctrl+B打開斷點設置框,以下圖: 


Figure 1設置高級位置斷點 
  2 而後選擇D行所在的斷點,而後點擊condition按鈕,在彈出對話框的最下面一個編輯框中輸入一個很大數目,具體視應用而定,這裏1000就夠了。 
  3 按F5從新運行程序,程序中斷。Ctrl+B打開斷點框,發現此斷點後跟隨一串說明:...487 times remaining。意思是還剩下487次沒有執行,那就是說執行到513(1000-487)次時候出錯的。所以,咱們按步驟2所講,更改此斷點的skip次數,將1000改成513。 
  4 再次從新運行程序,程序執行了513次循環,而後自動停在斷點處。這時,咱們就能夠仔細查看DoSome是如何返回0的。這樣,你就避免了手指的痛苦,節省了時間。 
  再看位置斷點其餘修飾條件。如Figure 1所示,在「Enter the expression to be evaluated:」下面,能夠輸入一些條件,當這些條件知足時,斷點才啓動。譬如,剛纔的程序,咱們須要i爲100時程序停下來,咱們就能夠輸入在編輯框中輸入「i==100」。 
  另外,若是在此編輯框中若是隻輸入變量名稱,則變量發生改變時,斷點纔會啓動。這對檢測一個變量什麼時候被修改很方便,特別對一些大程序。 
  用好位置斷點的修飾條件,能夠大大方便解決某些問題。

數據斷點(Data Breakpoint 
  軟件調試過程當中,有時會發現一些數據會莫名其妙的被修改掉(如一些數組的越界寫致使覆蓋了另外的變量),找出何處代碼致使這塊內存被更改是一件棘手的事情(若是沒有調試器的幫助)。恰當運用數據斷點能夠快速幫你定位什麼時候何處這個數據被修改。譬以下面一段程序:

#include "stdafx.h"

#include

 int main(int argc, char* argv[])

{

       char szName1[10];

       char szName2[4];

       strcpy(szName1,"shenzhen");             

       printf("%s\n", szName1);          //A

        strcpy(szName2, "vckbase");              //B

       printf("%s\n", szName1);

       printf("%s\n", szName2);

        return 0;

}

        這段程序的輸出是

             szName1: shenzhen

       szName1: ase

       szName2: vckbase

     szName1什麼時候被修改呢?由於沒有明顯的修改szName1代碼。咱們能夠首先在A行設置普通斷點,F5運行程序,程序停在A行。而後咱們再設置一個數據斷點。以下圖: 

Figure 2 數據斷點 
  F5繼續運行,程序停在B行,說明B處代碼修改了szName1。B處明明沒有修改szName1呀?但調試器指明是這一行,通常不會錯,因此仍是靜下心來看看程序,哦,你發現了:szName2只有4個字節,而strcpy了7個字節,因此覆寫了szName1。 
  數據斷點不僅是對變量改變有效,還能夠設置變量是否等於某個值。譬如,你能夠將Figure 2中紅圈處改成條件」szName2[0]==''''y''''「,那麼當szName2第一個字符爲y時斷點就會啓動。 
  能夠看出,數據斷點相對位置斷點一個很大的區別是不用明確指明在哪一行代碼設置斷點。

其餘調試手段:系統提供一系列特殊的函數或者宏來處理Debug版本相關的信息,以下:

宏名/函數名

說明

TRACE

使用方法和printf徹底一致,他在output框中輸出調試信息

ASSERT

它接收一個表達式,若是這個表達式爲TRUE,則無動做,不然中斷當前程序執行。對於系統中出現這個宏 致使的中斷,應該認爲你的函數調用未能知足系統的調用此函數的前提條件。例如,對於一個尚未建立的窗口調用SetWindowText等。

VERIFY

和ASSERT功能相似,所不一樣的是,在Release版本中,ASSERT不計算輸入的表達式的值,而VERIFY計算表達式的值。

 


Watch
VC支持查看變量、表達式和內存的值。全部這些觀察都必須是在斷點中斷的狀況下進行。
觀看變量的值最簡單,當斷點到達時,把光標移動到這個變量上,停留一會就能夠看到變量的值。
VC提供一種被成爲Watch的機制來觀看變量和表達式的值。在斷點狀態下,在變量上單擊右鍵,選擇Quick Watch, 就彈出一個對話框,顯示這個變量的值。
單擊Debug工具條上的Watch按鈕,就出現一個Watch視圖(Watch1,Watch2,Watch3,Watch4),在該視圖中輸入變量或者表達式,就能夠觀察 變量或者表達式的值。注意:這個表達式不能有反作用,例如++運算符絕對禁止用於這個表達式中,由於這個運算符將修改變量的值,致使 軟件的邏輯被破壞。
Memory
因爲指針指向的數組,Watch只能顯示第一個元素的值。爲了顯示數組的後續內容,或者要顯示一片內存的內容,可使用memory功能。在Debug工具條上點memory按鈕,就彈出一個對話框,在其中輸入地址,就能夠顯示該地址指向的內存的內容。
Varibles

Debug工具條上的Varibles按鈕彈出一個框,顯示全部當前執行上下文中可見的變量的值。特別是當前指令涉及的變量,以紅色顯示。
寄存器
Debug工具條上的Reigsters按鈕彈出一個框,顯示當前的全部寄存器的值。

 

調試技巧

一、VC++中F5進行調試運行

a)、在output Debug窗口中能夠看到用TRACE打印的信息

b)、 Call Stack窗口中能看到程序的調用堆棧

二、當Debug版本運行時發生崩潰,選擇retry進行調試,經過看Call Stack分析出錯的位置及緣由

三、使用映射文件調試

a)、建立映射文件:Project settings中link項,選中Generate mapfile,輸出程序代碼地址:/MAPINFO: LINES,獲得引出序號:/MAPINFO: EXPORTS。

b)、程序發佈時,應該把全部模塊的映射文件都存檔。

c)、查看映射文件:見」 經過崩潰地址找出源代碼的出錯行」文件。

四、能夠調試的Release版本

  Project settings中C++項的Debug Info選擇爲Program Database,Link項的Debug中選擇Debug Info和Microsoft format。

五、查看API的錯誤碼,在watch窗口輸入@err能夠查看或者@err,hr,其中」,hr」表示錯誤碼的說明。

六、Set Next Statement:該功能能夠直接跳轉到指定的代碼行執行,通常用來測試異常處理的代碼。

七、調試內存變量的變化:當內存發生變化時停下來。???

進程控制
VC容許被中斷的程序繼續運行、單步運行和運行到指定光標處,分別對應快捷鍵F五、F10/F11和CTRL+F10。各個快捷鍵功能以下: 
 

快捷鍵

說明

F5

調試/繼續運行

F10

單步,若是涉及到子函數,不進入子函數內部

F11

單步,若是涉及到子函數,進入子函數內部

CTRL+F10

運行到當前光標處。

F7

重建

F9

設置斷點/清除斷點

Ctrl+Shift+F9

清除全部斷點

Shift+F5

結束調試

Call Stack
調用堆棧反映了當前斷點處函數是被那些函數按照什麼順序調用的。單擊Debug工具條上的Call stack就顯示Call Stack對話框。在CallStack對話框中顯示了一個調用系列,最上面的是當前函數,往下依次是調用函數的上級函數。單擊這些函數名能夠跳到對應的函數中去。

關注
一個好的程序員不該該把全部的判斷交給編譯器和調試器,應該在程序中本身加以程序保護和錯誤定位,具體措施包括:

  • 對於全部有返回值的函數,都應該檢查返回值,除非你確信這個函數調用絕對不會出錯,或者不關心它是否出錯。
  • 一些函數返回錯誤,須要用其餘函數得到錯誤的具體信息。例如accept返回INVALID_SOCKET表示accept失敗,爲了查明 具體的失敗緣由,應該馬上用WSAGetLastError得到錯誤碼,並針對性的解決問題。
  • 有些函數經過異常機制拋出錯誤,應該用TRY-CATCH語句來檢查錯誤
  • 程序員對於能處理的錯誤,應該本身在底層處理,對於不能處理的,應該報告給用戶讓他們決定怎麼處理。若是程序出了異常, 卻不對返回值和其餘機制返回的錯誤信息進行判斷,只能是加大了找錯誤的難度。

另外:VC中要編制程序不該該一開始就寫cpp/h文件,而應該首先建立一個合適的工程。由於只有這樣,VC才能選擇合適的編譯、鏈接 選項。對於加入到工程中的cpp文件,應該檢查是否在第一行顯式的包含stdafx.h頭文件,這是Microsoft Visual Studio爲了加快編譯 速度而設置的預編譯頭文件。在這個#include "stdafx.h"行前面的全部代碼將被忽略,因此其餘頭文件應該在這一行後面被包含。
對於.c文件,因爲不能包含stdafx.h,所以能夠經過Project settings把它的預編譯頭設置爲「不使用」,方法是:

  • 彈出Project settings對話框
  • 選擇C/C++
  • Category選擇Precompilation Header
  • 選擇不使用預編譯頭。

便於調試的代碼風格

不用全局變量

全部變量都要初始化,成員變量在構造函數中初始化

儘可能使用const

詳盡的註釋

 

總結   調試最重要的仍是你要思考,要猜想你的程序可能出錯的地方,而後運用你的調試器來證明你的猜想。

相關文章
相關標籤/搜索