Visual?Studio實(shí)用調(diào)試技巧大全
一.什么是BUG??
Bug一詞的原意是蟲子,而在電腦系統(tǒng)或程序中隱藏著的一些未被發(fā)現(xiàn)的缺陷或問題,人們也叫它"bug"。這是為什么呢?這就要追溯到一個(gè)程序員與飛蛾的故事了。
Bug的創(chuàng)始人格蕾絲·赫柏(Grace Murray Hopper),是一位為美國海軍工作的電腦專家,也是最早將人類語言融入到電腦程序的人之一。而代表電腦程序出錯(cuò)的“bug” 這名字,正是由赫柏所取的。1947年9月9日,赫柏對(duì)Harvard Mark II設(shè)置好17000個(gè)繼電器進(jìn)行編程后,技術(shù)人員正在進(jìn)行整機(jī)運(yùn)行時(shí),它突然停止了工作。于是他們爬上去找原因,發(fā)現(xiàn)這臺(tái)巨大的計(jì)算機(jī)內(nèi)部一組繼電器的觸點(diǎn)之間有一只飛蛾,這顯然是由于飛蛾受光和熱的吸引,飛到了觸點(diǎn)上,然后被高電壓擊死。所以在報(bào)告中,赫柏用膠條貼上飛蛾,并把“bug”來表示“一個(gè)在電腦程序里的錯(cuò)誤”,“Bug”這個(gè)說法一直沿用到今天。
格蕾絲·赫柏的報(bào)告
二.調(diào)試及其重要性??
2.1 什么是調(diào)試
所有發(fā)生的事情都一定有跡可循,如果問心無愧,就不需要掩蓋也就沒有跡象了,如果問心有愧,就必然需要掩蓋,那就一定會(huì)有跡象,跡象越多就越容易順藤而上,這就是 推理的途徑。順著這條途徑順流而下就是犯罪,逆流而上,就是真相。
而我們程序員就好比一個(gè)偵探,一個(gè)用來尋找bug,修改bug的偵探。人們將這個(gè)過程叫做"Debug"(中文稱作"調(diào)試"),意即"捉蟲子"或"殺蟲子"。每一次調(diào)試都是嘗試破案的過程。
調(diào)試(英語:Debugging / Debug),又稱除錯(cuò),是發(fā)現(xiàn)和減少計(jì)算機(jī)程序或電子儀器設(shè)備中程序錯(cuò)誤的一個(gè)過程。
2.2 調(diào)試的基本步驟
- 發(fā)現(xiàn)程序錯(cuò)誤的存在
- 以隔離、消除等方式對(duì)錯(cuò)誤進(jìn)行定位
- 確定錯(cuò)誤產(chǎn)生的原因
- 提出糾正錯(cuò)誤的解決辦法
- 對(duì)程序錯(cuò)誤予以改正,重新測試
本文將詳細(xì)介紹在windows系統(tǒng)的 VS環(huán)境下的調(diào)試過程。
2.3 Debug與Release的介紹
在VS中,我們會(huì)發(fā)現(xiàn)我們的程序可以在兩個(gè)環(huán)境下運(yùn)行,這兩個(gè)環(huán)境就是Debug版本和Release版本,它們二者有何區(qū)別呢?
VS中的Debug與Release
Debug 通常稱為 調(diào)試版本,它包含調(diào)試信息,并且不作任何優(yōu)化, 便于程序員調(diào)試程序。
Release 稱為 發(fā)布版本,它往往是 進(jìn)行了各種優(yōu)化,使得程序在代碼大小和運(yùn)行速度上都是最優(yōu)的, 以便用戶很好地使用。
我們可以分別在兩種環(huán)境下編譯代碼生成對(duì)應(yīng)的可執(zhí)行程序如下:
顯然,Release版本的可執(zhí)行文件比Debug版本的小很多,說明編譯器對(duì)其作了優(yōu)化。
因此,我們?nèi)粘Kf的調(diào)試是在Debug版本的環(huán)境下進(jìn)行的,這是因?yàn)镽elease版本在不加以配置的情況下其調(diào)試信息會(huì)被編譯器優(yōu)化,對(duì)于程序員來說,調(diào)試基本都是在Debug環(huán)境下運(yùn)行(本文下面的調(diào)試也都是在Debug環(huán)境下進(jìn)行調(diào)試)。而對(duì)于測試人員來說,由于要站在用戶的角度上來測試程序是否能正常使用,因此測試是在Release版本的環(huán)境下進(jìn)行的。
??但不排除編寫的程序在Debug環(huán)境下沒有問題,在Release版本出現(xiàn)問題的情況。有些問題在Release環(huán)境下才會(huì)浮現(xiàn),這時(shí)我們就需要對(duì)Release版本進(jìn)行配置,進(jìn)行"調(diào)試"排查錯(cuò)誤。我們有以下幾種方法:
1.項(xiàng)目 -> 屬性 -> C/C++ -> 優(yōu)化 -> 禁用優(yōu)化,通過禁用優(yōu)化使其能生成調(diào)試信息進(jìn)行排錯(cuò),如下:
2.在項(xiàng)目 -> 屬性 -> C/C++ -> 優(yōu)化界面處,將選項(xiàng)逐個(gè)改為對(duì)應(yīng)的Debug選項(xiàng),如/O2改為/O1、/Oy改為/Oy-或者將運(yùn)行時(shí)間優(yōu)化改為程序大小優(yōu)化。注意的是,一般一次只改一個(gè)選項(xiàng),通過觀察改哪個(gè)選項(xiàng)時(shí)錯(cuò)誤消失來鎖定該選項(xiàng)相關(guān)的錯(cuò)誤,針對(duì)性地查找。個(gè)人較推薦這種方法。
話又說回來,Release版本下編譯器到底可能會(huì)做什么優(yōu)化呢?請(qǐng)看如下代碼:
#include <stdio.h> int main() { int i = 0; int arr[10] = {0}; for(i=0; i<=12; i++) { arr[i] = 0; printf("hehe\n"); } return 0; }
我們很容易就發(fā)現(xiàn)對(duì)數(shù)組進(jìn)行了越界訪問,當(dāng)程序運(yùn)行起來時(shí)應(yīng)該會(huì)崩潰。但是在Debug環(huán)境下我們發(fā)現(xiàn)程序并沒有崩潰而是陷入了死循環(huán):
我們運(yùn)行調(diào)試代碼轉(zhuǎn)到反匯編如下:
我們知道,數(shù)據(jù)在棧上的開辟是從高地址向低地址處開辟的,因此在Debug環(huán)境下變量i的地址比數(shù)組arr的地址高。而在數(shù)組內(nèi)部數(shù)據(jù)的存儲(chǔ)是從低地址向高地址的,因此首元素地址arr是在數(shù)組所在空間的低位,如下:
我們很驚訝的發(fā)現(xiàn)(arr+12)就是變量i的地址。當(dāng)循環(huán)過程中i等于12時(shí),此時(shí)將arr[i]改為0就等價(jià)于將i的值修改為0,然后i++后i等于1小于12,繼續(xù)進(jìn)行循環(huán),依次反復(fù)形成了死循環(huán)。如下:
整個(gè)過程簡化圖如下:
而在Release版本的環(huán)境下程序并不會(huì)出現(xiàn)死循環(huán)的問題:
我們可以打印出此時(shí)變量i和數(shù)組在棧上的地址如下:
我們發(fā)現(xiàn)此時(shí)變量i被編譯器優(yōu)化到低地址處,arr[12]存儲(chǔ)的值就不是i了,便不會(huì)出現(xiàn)死循環(huán)的情況。
綜上,以上代碼在Release版本下,編譯器使 變量在內(nèi)存中開辟的順序發(fā)生了變化,影響到了程序執(zhí)行的結(jié)果,這便是優(yōu)化帶來的好處。
三.windows環(huán)境下調(diào)試介紹?
首先第一步,我們需要將環(huán)境切換為Debug版本,才能進(jìn)行調(diào)試
3.1 常用快捷鍵介紹
以下是在調(diào)試過程中最為常用的幾個(gè)快捷鍵:
快捷鍵 | 功能 |
Ctrl+F5 | 開始執(zhí)行而不進(jìn)行調(diào)試。用于想讓程序直接運(yùn)行起來而不調(diào)試時(shí)。 |
F9 | 作用:創(chuàng)建斷點(diǎn)和取消斷點(diǎn) 斷點(diǎn):可以使程序在任何你想停下的地方停止執(zhí)行,繼而一步步執(zhí)行下去。我們可以在任何地方設(shè)置斷點(diǎn),一個(gè)程序也可以有多個(gè)斷點(diǎn)。 |
F10 | 逐過程,通常用來處理一個(gè)過程,一個(gè)過程可以是一次函數(shù)調(diào)用,或者是一條語句。 |
F11 | 逐語句,每次只執(zhí)行一條語句,我們可以通過這個(gè)快捷鍵使我們的執(zhí)行邏輯進(jìn)入函數(shù)內(nèi)部(這是F10所不具備的,F(xiàn)11是我們?cè)谡{(diào)試過程中最常用的) |
F5 | 啟動(dòng)調(diào)試到下一斷點(diǎn)處,需要配合F9進(jìn)行使用,如果程序沒有斷點(diǎn)則無異于Ctrl+F5 |
快捷鍵用法
我們還可以對(duì)某一斷點(diǎn)設(shè)置停止條件,方法是 右鍵斷點(diǎn)->單擊條件->輸入斷點(diǎn)條件->關(guān)閉
例如:我們需要讓程序停止在第4次循環(huán)處,我們可以輸入i==4
此時(shí)單擊F5運(yùn)行到斷點(diǎn),我們查看自動(dòng)窗口發(fā)現(xiàn)程序在停止時(shí)i的值為4
3.2 在調(diào)試過程中查看程序當(dāng)前信息
開啟調(diào)試后,我們可以在VS上方的調(diào)試->窗口看到許多用來查看數(shù)據(jù)信息的窗口:
3.3.1 監(jiān)視窗口
通過監(jiān)視窗口我們可以查看我們想要查看的 變量甚至是表達(dá)式的值在程序運(yùn)行過程中的變化,十分靈活,這是我們調(diào)試中用得最多的窗口之一。如下:
3.3.2 自動(dòng)窗口
打開自動(dòng)窗口后,編譯器會(huì)將一些可能需要觀察的變量顯示在窗口中,較為方便。其缺陷是可能無法顯示我們真正需要觀察的變量,并且 無法靈活顯示表達(dá)式等的值。如下:
3.3.3 局部變量窗口
打開局部變量變量窗口,會(huì)將程序中的所有 局部變量全部顯示出來。如下:
3.3.4 調(diào)用堆棧窗口
通過調(diào)用堆棧,可以清晰的反應(yīng) 函數(shù)的調(diào)用關(guān)系以及當(dāng)前調(diào)用所處的位置。如下:
通過調(diào)用堆棧窗口,我們可以發(fā)現(xiàn),show函數(shù)棧幀在main函數(shù)棧幀之上,即 show函數(shù)是由main函數(shù)調(diào)用的。并且可以看出此時(shí)show函數(shù)運(yùn)行到第20行。
3.3.5 內(nèi)存窗口
通過內(nèi)存窗口我們可以看到內(nèi)存中的信息,可以 觀察變量在內(nèi)存中的存儲(chǔ)情況。如下:
3.3.6 反匯編
我們可以查看當(dāng)前程序轉(zhuǎn)化后的匯編代碼,進(jìn)而從更底層的角度觀察程序的執(zhí)行過程。如下:
3.3.7 寄存器
通過寄存器窗口,我們可以觀察在當(dāng)前環(huán)境下CPU內(nèi)的寄存器的使用信息,如ebp棧底寄存器、esp棧頂寄存器等等。
3.3 調(diào)試實(shí)例
我們上面通過調(diào)試分析了數(shù)組越界陷入死循環(huán)的問題。下面,我們?cè)偻ㄟ^一道實(shí)例來掌握調(diào)試的技巧:
實(shí)現(xiàn)代碼:求 1!+2!+3! ...+ n! ;不考慮溢出。我們可能會(huì)寫出以下代碼:
#include<stdio.h> int main() { int i = 0; int sum = 0;//保存最終結(jié)果 int n = 0; int ret = 1;//保存n的階乘 scanf("%d", &n); for(i=1; i<=n; i++) { int j = 0; for(j=1; j<=i; j++) { ret *= j; } sum += ret; } printf("%d\n", sum); return 0; }
當(dāng)你輸入3時(shí),理論上應(yīng)該輸出9,但實(shí)際上程序卻輸出了15:
是什么問題導(dǎo)致出錯(cuò)了呢?這就需要我們動(dòng)手進(jìn)行調(diào)試。在調(diào)試之前我們可以先 預(yù)測問題的所在,比如 算階乘時(shí)出錯(cuò)、 求和時(shí)出錯(cuò)等等。做到心里有數(shù),而不是盲目的進(jìn)行調(diào)試。
我們可以在ret*=j處設(shè)置一個(gè)斷點(diǎn),打開監(jiān)視窗口監(jiān)視r(shí)et和sum觀察每個(gè)數(shù)階乘的值和累加后的值,如下:
我們單擊F10逐過程執(zhí)行,當(dāng)外層循環(huán)i的值為3時(shí),即計(jì)算3的階乘時(shí),我們發(fā)現(xiàn)ret的初始值并非為1,而是2的階乘。此時(shí)再計(jì)算3的階乘,就是2*1*2*3=12 != 6,結(jié)果出錯(cuò):
最后sum+ret的值就為15,與我們的輸出相符:
可見,每次在求階乘時(shí),我們都應(yīng)該把ret重置為1,正確的代碼如下:
int main() { int i = 0; int sum = 0;//保存最終結(jié)果 int n = 0; int ret = 1;//保存n的階乘 scanf("%d", &n); for (i = 1; i <= n; i++) { int j = 0; ret = 1; //將ret置1 for (j = 1; j <= i; j++) { ret *= j; } sum += ret; } printf("%d\n", sum); return 0; }
3.4 實(shí)踐出真知
任何事情都不可能一蹴而就,一定要多加練習(xí),才能熟練掌握調(diào)試技巧。
一個(gè)初學(xué)者可能80%的時(shí)間在寫代碼,20%的時(shí)間在進(jìn)行調(diào)試;而一個(gè)程序員,可能80%的時(shí)間在進(jìn)行調(diào)試,剩余20%的時(shí)間才是在寫代碼。
隨之學(xué)習(xí)的深入,后續(xù)可能會(huì)出現(xiàn)很多更加復(fù)雜的調(diào)試場景,如多線程等。只有打好基礎(chǔ),在未來才能融會(huì)貫通,利于不敗之地。
多多使用快捷鍵可以很大程度上提高效率。
四.如何寫出便于調(diào)試的優(yōu)秀代碼
4.1 什么是優(yōu)秀的代碼
1. 代碼運(yùn)行正常
2. bug很少
3. 效率高
4. 可讀性高
5. 可維護(hù)性高
6. 注釋清晰
7. 文檔齊全
4.2 常用coding(編碼)技巧
1.多使用assert斷言,可以告知你出錯(cuò)的位置
2.盡量使用const,避免意外修改
3.養(yǎng)成良好的編碼風(fēng)格
4.添加必要的注釋
5.避免編碼的陷阱
4.3 實(shí)例
試模擬實(shí)現(xiàn)一個(gè)strcpy函數(shù),盡量用到上述的編碼技巧。如下:
char* strcpy(char* dst, const char* src) //const修飾防止對(duì)源字符串進(jìn)行修改 { assert(dst && src); //避免傳入空指針 char cur = dst; //保存起始位置 //將src的字符一個(gè)個(gè)拷貝到det中,包括'\0' while ((*dst++ = *src++)!='\0') { ; } return cur; //返回目標(biāo)字符串,以便鏈?zhǔn)皆L問 }
五.編程中常見的錯(cuò)誤
5.1 編譯型錯(cuò)誤
在 編譯期間出現(xiàn)的錯(cuò)誤。直接看錯(cuò)誤提示信息(雙擊鼠標(biāo)),解決問題。或者憑借經(jīng)驗(yàn)就可以搞定。相對(duì)來說簡單。
5.2 鏈接型錯(cuò)誤
在 鏈接期間出現(xiàn)的錯(cuò)誤??村e(cuò)誤提示信息,主要在代碼中找到錯(cuò)誤信息中的標(biāo)識(shí)符,然后定位問題所在。一般是 標(biāo)識(shí)符名不存在或者 拼寫錯(cuò)誤。
5.3 運(yùn)行時(shí)出錯(cuò)
在 運(yùn)行期間出現(xiàn)的錯(cuò)誤。借助 調(diào)試,逐步定位問題。最難搞的一種錯(cuò)誤
不要害怕遇到錯(cuò)誤,每出現(xiàn)一次的錯(cuò)誤就是一次突破自我的機(jī)會(huì)。
學(xué)會(huì)積累排錯(cuò)經(jīng)驗(yàn),勇于嘗試。不經(jīng)風(fēng)雨 ?? ,怎能見彩虹 ??
以上,就是本期的全部內(nèi)容啦??
總結(jié)
到此這篇關(guān)于Visual Studio實(shí)用調(diào)試技巧大全的文章就介紹到這了,更多相關(guān)VS調(diào)試技巧內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
算法系列15天速成——第十五天 圖【下】(大結(jié)局)
今天是大結(jié)局,說下“圖”的最后一點(diǎn)東西,“最小生成樹“和”最短路徑“2013-11-11