VC調試技巧

一、 引言

本文主要介紹以下幾方面內容:

常見編譯錯誤
VC調試器
VC快捷鍵
VC項目文件說明
環境參數的設置

二、常見編譯錯誤 

1. Fatal Error C1010                        
unexpected end of file while looking for precompiled header directive
這一般是由於使用了參數/Yu“stdafx.h”,意思是在每個文件中都應該使用#include來包含這個頭文件。一般改正,就是在每個CPP文件中包含這個文件就可以。
l2. Error C2065            
 undeclared identifier
調用的方法或變量沒有定義。
l3. warning C4700
local variable ‘p’ used without having been initialized
使用的變量未經初始化。
l4. LNK2001
unresolved external symbol “symbol”(不確定的外部“符號”)。
如果連接程序不能在所有的庫和目標文件內找到所引用的函數、變量或標籤,將產生此錯誤消息。一般來說,發生錯誤的原因有兩個:
一是所引用的函數、變量不存在、拼寫不正確或者使用錯誤;
其次可能使用了不同版本的連接庫。

三、VC調試器

1. 調試環境的建立 

在VC中每當建立一個工程(Project)時,VC會爲你建立兩個版本,Release版本,和Debug版本(默認)。
Release版本是當程序完成後,準備發行時用來編譯的版本;
Debug版本是用在開發過程中進行調試時所用的版本。
Debug 版本當中,包含着Microsoft格式的調試信息,不進行任何代碼優化,而在Release版本對可執行程序的二進制代碼進行了優化,其中不包含任何的調試信息。
在新建立的工程中,你所看到是Debug版本,若要選擇Release版本,可以選擇菜單Project中的Setting命令,這時屏幕上面彈出Project Setting對話框,在Setting For下拉列表中選擇Release。

                                                                                           vc 圖

                                                                                           vs2005 圖

2. 斷點(breakpoint)

分類:在VC中,你可以設置多種類型的斷點,我們可以根據斷點的性質把斷點分爲三類:

(1)與位置有關的斷點(F9)

1) 與位置有關的邏輯斷點——到點條件成立才斷
    有的時候你可能並不需要程序每次運行到這兒都停下來,而是在滿足一定條件的情況下才停下來,這時你就需要設置一種與位置有關的邏輯斷點。要設置這種斷點我們只需要在Edit中選中Breakpoint項,則彈出Breakpoint對話框,選其中的Location標籤,在Location頁面中單擊Condition按鈕,在Expression編輯框中寫出你的邏輯表達式,如X>=3或a+b>25,最後按OK返回。這種斷點主要是由其位置發生作用的,但也結合了邏輯條件,使之更靈活。如圖:

2) 在在彙編代碼上設立斷點:

  有時我們需要進入程序的彙編代碼,更深入地調試程序,因此要在彙編代碼上設立斷點。要設立這種斷點我們只需選擇Debug Window中的Disassembly子命令,這時彙編窗口將會出現在屏幕上。在彙編窗口中你將看到對應於源程序的彙編代碼,其中源程序是用黑體字顯示,下面是且對應的彙編代碼。要設立斷點,我們只需將光標移到你想設斷點處然後點擊工具條上的Insert/Remove Breakpoints 按鈕,此後你將會看到一個紅圓點出現在該彙編代碼的右邊。
(2)與邏輯條件有關的斷點;
1)邏輯條件觸發斷點的設置 ;
– 選中Breakpoint對話框中的DATA標籤;
– DATA頁面中的Expression編輯框中寫出你的邏輯表達式,如(6==sum);


l2)監視表達式發生變化斷點;
 – 選中Breakpoint對話框中的DATA標籤;
 – 在Expression編輯框中寫出你需要監視的表達式;
l3)監視數組發生變化的斷點
– 選中Breakpoint對話框中的DATA標籤;
– 在Expression編輯框中寫出你需要監視數組名;
– 在Number of Elements 編輯框輸入你需要監視數組元素的個數;

l4)監視由指針指向的數組發生變化的斷點;
– 選中Breakpoint對話框中的DATA標籤;
– 在Expression編輯框中輸入形如*pointname,其中*pointname爲指針變量名;
– 在Number of Elements 編輯框輸入你需要監視數組元素的個數;
l 5)監視外部變量發生變化的斷點;
– 選中Breakpoint對話框中的DATA標籤;
– 在Expression編輯框中輸入變量名;
– 點擊在Expression編輯框的右邊的下拉鍵頭;
– 選取Advanced選項,這時Advanced Breakpoint 對話框出現;
– 在context框中輸入對應的函數名和(如果需要的話)文件名;
(3)與WINDOWS消息有關的斷點;
l注意:此類斷點只能工作在x86 或 Pentium 系統上。
– 選中Breakpoint對話框中的Message標籤;
– 在Break At WndProc 編輯框中輸入Windows 函數的名稱;
– 在Set One Breakpoint From Each Message To Watch下拉列表框中選擇對應的消息;

應用斷點:如何控制程序的運行
當我們從菜單Build到子菜單Start Debuging 選擇Go 程序開始運行在Debug狀態下,程序會由於斷點而停頓下來後,可以看到有一個小箭頭,它指向即將執行的代碼。隨後,我們就可以按要求來控制程序的運行:其中有四條命令:Step over, step Into , Step Out ,Run to Cursor 。
Step over 的功能是運行當前箭頭指向的代碼(只運行一條代碼)。F10
Step Into的功能是如果當前箭頭所指的代碼是一個函數的調用,則用Step Into 進入該函數進行單步執行。F11
Step Out的功能是如當前箭頭所指向的代碼是在某一函數內,用它使程序運行至函數返回處。Shift F11
Run to Cursor的功能是使程序運行至光標所指的代碼處。Ctrl F10

3. VC調試器——查勘現場 

l查看工具的使用
    調試過程中最重要的是要觀察程序在運行過程中的狀態,這樣我們才能找出程序的錯誤之處。這裏所說的狀態包括各變量的值,寄存中的值,內存中的值,堆棧中的值 ,爲此我們需要利用各種工具來幫助我們察看程序的狀態。

先看一下這張圖,然後再看下面的介紹:

(1) 彈出式調試信息泡泡(Data Tips Pop_up Information)
當程序在斷點停下來後,要觀察一個變量或表達式的值的最容易的方法是利用調試信息泡泡。要看一個變量的值,只需在源程序窗口中,將鼠標放到該變量上,你將會看到一個信息泡泡彈出,其中顯示出該變量的值。要查看一個表達式的值,先選中該表達式,仍後將鼠標放到選中的表達式上,同樣會看到一個信息泡泡彈出以顯示該表達式的值。
l
(2)變量窗口(VARIABLE WINDOW)——看程序中的變量
斷點處或其附近被訪問的變量的當前值。Variables窗口的下部有三個標籤:
AUTO:顯示變量和函數返回值;
LOCAL:顯示當前函數的局部變量;
THIS:在一個C++程序中,顯示this指針當前指向的對象;
(3)觀察窗口(WATCH WINDOW)——索要變量或表達式的當前值
被調試器直接跟蹤的變量和表達式的當前值。在Watch窗口中指定那些你在程序暫停時總想知道他們當前值的那些變量。
在觀察窗口中雙擊Name欄的某一空行,輸入你要查看的變量名或表達式,回車後你將會看到對應的值。觀察窗口可有多頁,分別對應於標籤Watch1、Watch2、Watch3。假如你輸入的表達式是一個結構或是一個對象,你可以用鼠標點取表達式右邊的形如 + ,以進一步觀察其中的成員變量的值。
(4)快速查看變量對話框(quick watch)
在快速查看變量對話框中你可以象利用觀察窗口一樣來查看變量或表達式的值。但我們還可以利用它來改變運行過程中的變量,具體操作如下:
1)在Debug 菜單,選擇Quick Watch命令,這時屏幕上將會出現Quick Watch 對話框;
2)在Expression 編輯框中輸入變量名,按回車;
3)在Current Value 格子中將出現變量名及其當前對應的值;
4)如要改變該變量的值只需雙擊該變量對應的Name 欄,輸入你要改變的值;
5)如要把該變量加入到觀察窗口中,點擊Add watch 按鈕;
6)點擊Close 按鈕返回;

Shift F9 如圖:

(5)查看內存中的值
一個指定地址的內存堆。
在Memory窗口中,在Address 編輯框中輸入你要查看的內存地址,對應內存地址中的值將被顯示出來;

(6)查看或改變CPU寄存器中的值
1) 在Registers 窗口中,信息以 Register = Value 的形式顯示,其中Register 代表寄存器的名字,Value 代表寄存器中的值;
2)如果你要修改某一個寄存器的值,用TAB鍵或鼠標將光標移到你想改變的值的右邊,然後輸入你想要的值。
l在寄存器中,有一類特殊的寄存器稱爲標誌寄存器,其中有八個標誌位:
OV是溢出標誌
UP是方向標誌
EI是中斷使能標誌
Sign 是符號標誌
Zero是零標誌
Parity是奇偶較驗標誌
Carry 是進位標誌

(7)查看Call Stack
在Call Stack窗口中可看到還未返回的調用函數列表,調用棧給出從嵌套函數調用一直到斷點位置的執行路徑。
(8)查看Disassembly窗口
編譯代碼的彙編語言翻譯,補充道屏幕上的源窗口中。“Disassembly”指的是把程序中的機器代碼轉換爲相應的彙編指令。

四、VC快捷鍵 

1. VC編輯快捷鍵

按下Alt  鍵不放,點擊鼠標左鍵拖動,可以選擇文本塊、可選擇列; 
按着Ctrl鍵不放,單擊一個單詞,可以選擇一個單詞,或雙擊;
將光標移在開始位置,按住shift點擊鼠標左鍵可選擇一段(在IE瀏覽其中照樣可用,看不到光標而已);
雙擊鼠標左鍵可選擇一個單詞; 
按住shift+上下方向鍵可選擇行; 
按住ctrl+shift+左右方向鍵可選擇一個單詞;
按Ctrl+C可COPY光標所在的這一行;
按住shift+[End]可選擇本行;
F3                 向下  
Tab         選擇的行全部右移一個 TAB鍵的寬度
Alt + F8           按定義的格式重新排列選定的文本。 
shift + F3         向上 
shift + Tab        選擇的行全部左移一個 TAB鍵的寬度 
Ctrl + F           查找、搜索 
Ctrl + H           替換 
Ctrl + G           到某行
Ctrl + U           選擇部分變爲小寫
Ctrl + shift + U   選擇部分變爲大寫
Ctrl+J                向上搜索最近的#if/#else/#ifdef/#endif  
Ctrl+K             向下搜索最近的#if/#else/#ifdef/#endif
Ctrl+]             自動配對大括號或小括號。但有時不對應,是因爲其他字符有“{”或“}”存在

2. VC調試快捷鍵

F5          Go    運行碰到斷點就停
F7          Build    編譯鏈接
F9          Add/Remove Breakpoint 插入/刪除斷點 
F10         Step Over             一步步運行,碰到函數不進去 
F11         Step Into            一步步運行,碰到函數就進去(當然那些WinAPI由於在Dll中,就進不去了!)
Ctrl + F5   Execute Program       運行
Ctrl + F7   Compile               編譯一個源文件
Ctrl + F10  Run to Cursor         調試到光標所在位置
Shift + F5  Stop Debugging        停止調試     
Shift + F9  Quick Watch           快速查看/修改變量信息
Shift + F11 Step Out              從當前函數中跳出
Alt + F9    Breakpoint            高級斷點設置
Alt + 7     Call Stack            堆棧窗口,可以察看函數調用情況
Alt + 4     Variables             當前運行代碼行的變量或者返回值信息
Alt + 3     Watch                可以把關注的變量拖入窗口中,察看/修改變量信息。

五、VC項目文件說明

.opt工程關於開發環境的參數文件,如工具條位置等信息;    
.dsp(DeveloperStudio Project)項目文件,文本格式,項目參數配置文件,不熟悉的話不要手工修改;
.dsw(DeveloperStudio Workspace)是工作區文件,其他特點和dsp差不多,可以由.dsp生成;
.plg是編譯信息文件,編譯時的error和warning信息文件(實際上是一個html文件),一般用處不大,在Tools->Options裏面有個選項可以控制這個文件的生成; 
以上是我們工程編譯時候最常見的,下邊這些可以不用關心,瞭解就可以了。
.aps(AppStudio File),資源輔助文件,二進制格式,一般不用去管他;
.clw  ClassWizard信息文件,實際上是INI文件的格式,有興趣可以研究一下,有時候ClassWizard出問題,手工修改CLW文件可以解決,如果此文件不存在的話,每次用ClassWizard的時候會提示你是否重建;
.hpj(Help Project)是生成幫助文件的工程,用microsfot Help Compiler可以處理;
.mdp(Microsoft DevStudio Project)是舊版本的項目文件,如果要打開此文件的話,會提示你是否轉換成新的DSP格式;
.map是執行文件的映像信息紀錄文件,除非對系統底層非常熟悉,這個文件一般用不着;
.pch(Pre-Compiled  File)是預編譯文件,可以加快編譯速度,但是文件非常大;
.pdb(Program Database)記錄了程序有關的一些數據和調試信息,在調試的時候可能有用;
.exp只有在編譯DLL的時候纔會生成,記錄了DLL文件中的一些信息,一般也沒什麼用;
注:如果你想與別人共享你的源代碼項目,但是把整個項目做拷貝又太大。你完全可以刪掉以下文件: 
 .dsw、.ncb、.opt、.aps、.clw、. plg文件以及Debug、Release目錄下的所有文件

六、環境參數的設置(Project Setting —> Alt F7)

VC的處理流程,大致分爲兩步:
編譯:源文件通過預編譯和編譯生成了.obj
              文件;
鏈接:所有.obj文件和.lib文件通過鏈接生成
              .exe文件或.dll文件;
下面,我們分別討論這兩個步驟的一些細節。
lProject->Settings->C/C++
Project Option中各個參數代表的意義,可以參考MSDN,比如/nologo表示編譯時不在輸出窗口顯示這些設置(我們可以把這個參數去掉來看看效果)等等。
一般我們不會直接修改這些設置,而是通過這一頁最上面的Category中的各項來完成。
General
Warning level 控制警告信息,其中Level1是最嚴重的級別
Warnings as errors 將警告信息當作錯誤處理
Optimizations 代碼優化,可以在Category的Optimizations項中進行更細的設置
Generate browse info 用以生成.sbr文件,記錄類、變量等符號信息,可以在Category的Listing Files項中進行更多的設置
Debug info 生成調試信息 None 不產生任何調試信息(編譯比較快)
Line Numbers Only 僅生成全局的和外部符號的調試信息到.OBJ文件或.EXE文件,減小目標文件的尺寸
C7 Compatible 記錄調試器用到的所有符號信息到.OBJ文件和.EXE文件
Program Database 創建.PDB文件記錄所有調試信息
Program Database for Edit and Continue 創建.PDB文件記錄所有調試信息,並且支持調試時編輯
C++ Language

pointer_to_member representation 用來設置類定義/引用的先後關係,一般爲Best-Case Always表示在引用類之前該類肯定已經定義了
Enable Exception Handling 進行同步的異常處理
Enable Run-Time Type Information 迫使編譯器增加代碼在運行時進行對象類型檢查
Disable Construction Displacements 設置類構造/析構函數調用虛函數問題
lCode Generation
Processor 表示代碼指令優化,可以爲80386、80486、Pentium、Pentium Pro,或者Blend表示混合以上各種優化

 

 

Use run-time library 用以指定程序運行時使用的運行時庫(單線程或多線程,Debug版本或Release版本),有一個原則就是,一個進程不要同時使用幾個版本的運行時庫,連接了單線程庫就不支持多線程調用,連接了多線程庫就要求創建多線程的應用程序 Single-Threaded 靜態連接LIBC.LIB庫
Debug Single-Threaded 靜態連接LIBCD.LIB庫
Multithreaded 靜態連接LIBCMT.LIB庫
Debug Multithreaded 靜態連接LIBCMTD.LIB庫
Multithreaded DLL 動態連接MSVCRT.DLL庫
Debug Multithreaded DLL 動態連接MSVCRTD.DLL庫
Calling convention 用來設定調用約定,有三種:__cdecl、__fastcall和__stdcall。各種調用約定的主要區別在於,函數調用時,函數的參數是從左到右壓入堆棧還是從右到左壓入堆棧;在函數返回時,由函數的調用者來清理壓入堆棧的參數還是由函數本身來清理;以及在編譯時對函數名進行的命名修飾(可以通過Listing Files看到各種命名修飾方式)
Struct member alignment 用以指定數據結構中的成員變量在內存中是按幾字節對齊的,根據計算機數據總線的位數,不同的對齊方式存取數據的速度不一樣。這個參數對數據包網絡傳輸等應用尤爲重要,不是存取速度問題,而是數據位的精確定義問題,一般在程序中使用#pragma pack來指定
Customize

Disable Language Extensions 表示不使用微軟爲標C做的
Eliminate Duplicate Strings
主要用於字符串化(將字符串放到充池裏以省空),使用個參數,使得
char *sBuffer = "This is a character buffer";
char *tBuffer = "This is a character buffer";
sBuffer和tBuffer指向的是同一內存空
Enable Function-Level Linking
訴編譯器將各個函數按打包格式編譯
Enables minimal rebuild 保存關聯信息到.IDB文件,使編譯器只最新動過的源文件行重編譯,提高編譯速度
Enable Incremental Compilation .IDB文件保存的信息,只重編譯最新改動過的函數
Suppress Startup Banner and Information Messages
用以控制參數是否在output窗口
Listing Files
Generate browse info 上面已經提到過,這裏可以進行更多的設置
Exclude Local Variables from Browse Info 是否將局部變量的信息放到.SBR文件中
Listing file type 可以設置生成的列表信息文件的內容 Assembly-Only Listing 僅生成彙編代碼文件(.ASM擴展名)
Assembly With Machine Code 生成機器代碼和彙編代碼文件(.COD擴展名)
Assembly With Source Code 生成源代碼和彙編代碼文件(.ASM擴展名)
Assembly, Machine Code, and Source 生成機器碼、源代碼和彙編代碼文件(.COD擴展名)
Listing file name 生成的信息文件的路徑,一般爲Debug或Release目錄下,生成的文件名自動取源文件的文件名
Optimizations
Maximize Speed 生成最快速的代碼
Minimize Size 生成最小尺寸的程序
Customize 定製優化 Assume No Aliasing 不使用別名(提高速度)
Assume Aliasing Across Function Calls 僅函數內部不使用別名
Global Optimizations
全局優化,比如經常用到的變量使用寄存器保存,或者循環內的計算優化,如
i = -100;while( i < 0 ){i += x + y;}
會被優化爲i = -100;t = x + y;
while( i < 0 ){i += t;}
Generate Intrinsic Functions 使用內部函數替換一些函數調用(提高速度)
Improve Float Consistency 浮點運算方面的優化
Favor Small Code 程序(exe或dll)尺寸優化優先於代碼速度優化
Favor Fast Code 程序(exe或dll)代碼速度優化優先於尺寸優化
Frame-Pointer Omission 不使用幀指針,以提高函數調用速度
Full Optimization 組合了幾種參數,以生成最快的程序代碼
Inline function expansion 內聯函數擴展的三種優化(使用內聯可以節省函數調用的開銷,加快程序速度) Disable 不使用內聯
Only __inline 僅函數定義前有inline或__inline標記使用內聯
Any Suitable 除了inline或__inline標記的函數外,編譯器“覺得”應該使用內聯的函數,都使用內聯
lPrecompiled Headers
預編譯頭文件的設置。使用預編譯可以提高重複編譯的速度。VC一般將一些公共的、不大變動的頭文件(比如afxwin.h等)集中放到stdafx.h中,這一部分代碼就不必每次都重新編譯(除非是Rebuild All) 
lPreprocessor

預編譯處理。可以定義/解除定義一些常量,Additional include directories可以指定額外的包含目錄,一般是相對於本項目的目錄,如..Include。 
lProject->Settings->Link


 General
一些總體設置,可以設置生成的文件路徑、文件名、連接的庫文件。
Generate debug info 生成Debug信息到.PDB文件(具體格式可以在Category->Debug中設置)
Ignore All Default Libraries 放棄所有默認的庫連接
Link Incrementally 通過生成. ILK文件實現遞增式連接以提高後續連接速度,但一般這種方式下生成的文件(EXE或DLL)較大
Generate Mapfile 生成.MAP文件記錄模塊相關信息
Enable Profiling 這個參數通常與Generate Mapfile參數同時使用,而且如果產生Debug信息的話,不能用.PDB文件,而且必須用Microsoft Format。
lCustomize
這裏可以進行使用程序數據庫文件的設置。
Force File Output:強制產生輸出文件(EXE或DLL);
Print Progress Messages:可以將連接過程中的進度信息輸出到Output窗口;
lDebug
設置是否生成調試信息,以及調試信息的格式。
格式可以有Microsoft Format、COFF Format(Common Object File Format)和Both Formats三種選擇;Separate Types表示將Debug格式信息以獨立的.PDB文件存放,還是直接放在各個源文件的.PDB文件中。選中的話,表示採用後者的方式,這種方式調試啓動比較快。
lInput
這裏可以指定要鏈接的庫文件,放棄鏈接的庫文件。還可以增加額外的庫文件目錄,一般是相對於本項目的目錄,如..Lib。
Force Symbol References可以指定連接特定符號定義的庫。
lOutput
lBase Address可以改變程序默認的基地址(EXE文件默認爲0×400000,DLL默認爲0×10000000)。操作系統裝載一個程序時總是試着先從這個基地址開始。
lStack allocations,用以設置程序使用的堆棧大小(請使用十進制),默認爲1兆字節。Version Information告訴連接器在EXE或DLL文件的開始部分放上版本號。
Entry-Point Symbol
可以指定程序的入口地址,一般爲一個函數名(且必須採用__stdcall調用約定)。一般Win32的程序,EXE的入口爲WinMain,DLL的入口爲DllEntryPoint;最好讓連接器自動設置程序的入口點。默認情況下,通過一個C的運行時庫函數來實現:控制檯程序採用mainCRTStartup (或wmainCRTStartup)去調用程序的main (或wmain)函數;Windows程序採用WinMainCRTStartup (或 wWinMainCRTStartup)調用程序的WinMain
(或 wWinMain,必須採用__stdcall調用約定);DLL採用_DllMainCRTStartup調用DllMain函數(必須採用__stdcall調用約定)。
其他一些參數的設置
(1)Project->Settings->General,可以設置連接MFC庫的方式(靜態或動態)。如果是動態連接,在你的軟件發佈時不要忘了帶上MFC的DLL。
(2)Project->Settings->Debug,可以設置調試時運行的可執行文件,以及命令行參數等。
(3)Project->Settings->Custom Build,可以設置編譯/連接成功後自動執行一些操作。比較有用的是,寫COM時希望VC對編譯通過的COM文件自動註冊,可以如下設置:
Description: Register COM
Commands: regsvr32 /s /c $(TargetPath)
echo regsvr32 exe.time > $(TargetDir)$(TargetName).trg
Outputs: $(TargetDir)$(TargetName).trg
(4)Tools->Options->Directories,設置系統的Include、Library路徑。
l注:值得注意的是,上面各個參數是大小寫敏感的;在參數後加上“-”表示該參數無效;各個參數值選項有“*”的表示爲該參數的默認值;可以使用頁右上角的“Reset”按鈕來恢復該頁的所有默認設置。

七、總結

VC提供了一個很好的Debug工具,其提供的調用棧、條件斷點、數據斷點、反彙編等工具足夠強大,足夠應付平常的Bug,程序員不僅應具有對Bug的定位能力,更爲主要的還是對於調試工具的掌握、使用的能力。所謂“磨刀不誤砍柴工”,在開發之前或者開發閒暇時,好好的研究一下一些開發、調試工具不失爲提升這種能力的好辦法。能靜下心來思考一下這些工具的工作原理就更好了,這樣不僅能幫助你在編程的時候預見Bug,並且對你提高你的編程技巧也會有所幫助。
發佈了0 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章