Visual?Studio實(shí)用調(diào)試技巧大全
一.什么是BUG??
Bug一詞的原意是蟲(chóng)子,而在電腦系統(tǒng)或程序中隱藏著的一些未被發(fā)現(xiàn)的缺陷或問(wèn)題,人們也叫它"bug"。這是為什么呢?這就要追溯到一個(gè)程序員與飛蛾的故事了。
Bug的創(chuàng)始人格蕾絲·赫柏(Grace Murray Hopper),是一位為美國(guó)海軍工作的電腦專家,也是最早將人類語(yǔ)言融入到電腦程序的人之一。而代表電腦程序出錯(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”來(lái)表示“一個(gè)在電腦程序里的錯(cuò)誤”,“Bug”這個(gè)說(shuō)法一直沿用到今天。

格蕾絲·赫柏的報(bào)告
二.調(diào)試及其重要性??
2.1 什么是調(diào)試
所有發(fā)生的事情都一定有跡可循,如果問(wèn)心無(wú)愧,就不需要掩蓋也就沒(méi)有跡象了,如果問(wèn)心有愧,就必然需要掩蓋,那就一定會(huì)有跡象,跡象越多就越容易順藤而上,這就是 推理的途徑。順著這條途徑順流而下就是犯罪,逆流而上,就是真相。
而我們程序員就好比一個(gè)偵探,一個(gè)用來(lái)尋找bug,修改bug的偵探。人們將這個(gè)過(guò)程叫做"Debug"(中文稱作"調(diào)試"),意即"捉蟲(chóng)子"或"殺蟲(chóng)子"。每一次調(diào)試都是嘗試破案的過(guò)程。
調(diào)試(英語(yǔ):Debugging / Debug),又稱除錯(cuò),是發(fā)現(xiàn)和減少計(jì)算機(jī)程序或電子儀器設(shè)備中程序錯(cuò)誤的一個(gè)過(guò)程。
2.2 調(diào)試的基本步驟
- 發(fā)現(xiàn)程序錯(cuò)誤的存在
- 以隔離、消除等方式對(duì)錯(cuò)誤進(jìn)行定位
- 確定錯(cuò)誤產(chǎn)生的原因
- 提出糾正錯(cuò)誤的解決辦法
- 對(duì)程序錯(cuò)誤予以改正,重新測(cè)試
本文將詳細(xì)介紹在windows系統(tǒng)的 VS環(huán)境下的調(diào)試過(guò)程。
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版本的小很多,說(shuō)明編譯器對(duì)其作了優(yōu)化。
因此,我們?nèi)粘Kf(shuō)的調(diào)試是在Debug版本的環(huán)境下進(jìn)行的,這是因?yàn)镽elease版本在不加以配置的情況下其調(diào)試信息會(huì)被編譯器優(yōu)化,對(duì)于程序員來(lái)說(shuō),調(diào)試基本都是在Debug環(huán)境下運(yùn)行(本文下面的調(diào)試也都是在Debug環(huán)境下進(jìn)行調(diào)試)。而對(duì)于測(cè)試人員來(lái)說(shuō),由于要站在用戶的角度上來(lái)測(cè)試程序是否能正常使用,因此測(cè)試是在Release版本的環(huán)境下進(jìn)行的。
??但不排除編寫的程序在Debug環(huán)境下沒(méi)有問(wèn)題,在Release版本出現(xiàn)問(wèn)題的情況。有些問(wè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)化,通過(guò)禁用優(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),通過(guò)觀察改哪個(gè)選項(xiàng)時(shí)錯(cuò)誤消失來(lái)鎖定該選項(xiàng)相關(guān)的錯(cuò)誤,針對(duì)性地查找。個(gè)人較推薦這種方法。

話又說(shuō)回來(lái),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)行了越界訪問(wèn),當(dāng)程序運(yùn)行起來(lái)時(shí)應(yīng)該會(huì)崩潰。但是在Debug環(huán)境下我們發(fā)現(xiàn)程序并沒(méi)有崩潰而是陷入了死循環(huán):

我們運(yùn)行調(diào)試代碼轉(zhuǎn)到反匯編如下:

我們知道,數(shù)據(jù)在棧上的開(kāi)辟是從高地址向低地址處開(kāi)辟的,因此在Debug環(huán)境下變量i的地址比數(shù)組arr的地址高。而在數(shù)組內(nèi)部數(shù)據(jù)的存儲(chǔ)是從低地址向高地址的,因此首元素地址arr是在數(shù)組所在空間的低位,如下:

我們很驚訝的發(fā)現(xiàn)(arr+12)就是變量i的地址。當(dāng)循環(huán)過(guò)程中i等于12時(shí),此時(shí)將arr[i]改為0就等價(jià)于將i的值修改為0,然后i++后i等于1小于12,繼續(xù)進(jìn)行循環(huán),依次反復(fù)形成了死循環(huán)。如下:

整個(gè)過(guò)程簡(jiǎn)化圖如下:

而在Release版本的環(huán)境下程序并不會(huì)出現(xiàn)死循環(huán)的問(wèn)題:

我們可以打印出此時(shí)變量i和數(shù)組在棧上的地址如下:

我們發(fā)現(xiàn)此時(shí)變量i被編譯器優(yōu)化到低地址處,arr[12]存儲(chǔ)的值就不是i了,便不會(huì)出現(xiàn)死循環(huán)的情況。
綜上,以上代碼在Release版本下,編譯器使 變量在內(nèi)存中開(kāi)辟的順序發(fā)生了變化,影響到了程序執(zhí)行的結(jié)果,這便是優(yōu)化帶來(lái)的好處。
三.windows環(huán)境下調(diào)試介紹?
首先第一步,我們需要將環(huán)境切換為Debug版本,才能進(jìn)行調(diào)試

3.1 常用快捷鍵介紹
以下是在調(diào)試過(guò)程中最為常用的幾個(gè)快捷鍵:
快捷鍵 | 功能 |
Ctrl+F5 | 開(kāi)始執(zhí)行而不進(jìn)行調(diào)試。用于想讓程序直接運(yùn)行起來(lái)而不調(diào)試時(shí)。 |
F9 | 作用:創(chuàng)建斷點(diǎn)和取消斷點(diǎn) 斷點(diǎn):可以使程序在任何你想停下的地方停止執(zhí)行,繼而一步步執(zhí)行下去。我們可以在任何地方設(shè)置斷點(diǎn),一個(gè)程序也可以有多個(gè)斷點(diǎn)。 |
F10 | 逐過(guò)程,通常用來(lái)處理一個(gè)過(guò)程,一個(gè)過(guò)程可以是一次函數(shù)調(diào)用,或者是一條語(yǔ)句。 |
F11 | 逐語(yǔ)句,每次只執(zhí)行一條語(yǔ)句,我們可以通過(guò)這個(gè)快捷鍵使我們的執(zhí)行邏輯進(jìn)入函數(shù)內(nèi)部(這是F10所不具備的,F(xiàn)11是我們?cè)谡{(diào)試過(guò)程中最常用的) |
F5 | 啟動(dòng)調(diào)試到下一斷點(diǎn)處,需要配合F9進(jìn)行使用,如果程序沒(méi)有斷點(diǎn)則無(wú)異于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)試過(guò)程中查看程序當(dāng)前信息
開(kāi)啟調(diào)試后,我們可以在VS上方的調(diào)試->窗口看到許多用來(lái)查看數(shù)據(jù)信息的窗口:

3.3.1 監(jiān)視窗口
通過(guò)監(jiān)視窗口我們可以查看我們想要查看的 變量甚至是表達(dá)式的值在程序運(yùn)行過(guò)程中的變化,十分靈活,這是我們調(diào)試中用得最多的窗口之一。如下:


3.3.2 自動(dòng)窗口
打開(kāi)自動(dòng)窗口后,編譯器會(huì)將一些可能需要觀察的變量顯示在窗口中,較為方便。其缺陷是可能無(wú)法顯示我們真正需要觀察的變量,并且 無(wú)法靈活顯示表達(dá)式等的值。如下:


3.3.3 局部變量窗口
打開(kāi)局部變量變量窗口,會(huì)將程序中的所有 局部變量全部顯示出來(lái)。如下:

3.3.4 調(diào)用堆棧窗口
通過(guò)調(diào)用堆棧,可以清晰的反應(yīng) 函數(shù)的調(diào)用關(guān)系以及當(dāng)前調(diào)用所處的位置。如下:

通過(guò)調(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)存窗口
通過(guò)內(nèi)存窗口我們可以看到內(nèi)存中的信息,可以 觀察變量在內(nèi)存中的存儲(chǔ)情況。如下:


3.3.6 反匯編
我們可以查看當(dāng)前程序轉(zhuǎn)化后的匯編代碼,進(jìn)而從更底層的角度觀察程序的執(zhí)行過(guò)程。如下:

3.3.7 寄存器
通過(guò)寄存器窗口,我們可以觀察在當(dāng)前環(huán)境下CPU內(nèi)的寄存器的使用信息,如ebp棧底寄存器、esp棧頂寄存器等等。

3.3 調(diào)試實(shí)例
我們上面通過(guò)調(diào)試分析了數(shù)組越界陷入死循環(huán)的問(wèn)題。下面,我們?cè)偻ㄟ^(guò)一道實(shí)例來(lái)掌握調(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:

是什么問(wèn)題導(dǎo)致出錯(cuò)了呢?這就需要我們動(dòng)手進(jìn)行調(diào)試。在調(diào)試之前我們可以先 預(yù)測(cè)問(wèn)題的所在,比如 算階乘時(shí)出錯(cuò)、 求和時(shí)出錯(cuò)等等。做到心里有數(shù),而不是盲目的進(jìn)行調(diào)試。
我們可以在ret*=j處設(shè)置一個(gè)斷點(diǎn),打開(kāi)監(jiān)視窗口監(jiān)視r(shí)et和sum觀察每個(gè)數(shù)階乘的值和累加后的值,如下:

我們單擊F10逐過(guò)程執(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,與我們的輸出相符:

可見(jiàn),每次在求階乘時(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ǎng)景,如多線程等。只有打好基礎(chǔ),在未來(lái)才能融會(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問(wèn)
}五.編程中常見(jiàn)的錯(cuò)誤
5.1 編譯型錯(cuò)誤
在 編譯期間出現(xiàn)的錯(cuò)誤。直接看錯(cuò)誤提示信息(雙擊鼠標(biāo)),解決問(wèn)題?;蛘邞{借經(jīng)驗(yàn)就可以搞定。相對(duì)來(lái)說(shuō)簡(jiǎn)單。
5.2 鏈接型錯(cuò)誤
在 鏈接期間出現(xiàn)的錯(cuò)誤。看錯(cuò)誤提示信息,主要在代碼中找到錯(cuò)誤信息中的標(biāo)識(shí)符,然后定位問(wèn)題所在。一般是 標(biāo)識(shí)符名不存在或者 拼寫錯(cuò)誤。
5.3 運(yùn)行時(shí)出錯(cuò)
在 運(yùn)行期間出現(xiàn)的錯(cuò)誤。借助 調(diào)試,逐步定位問(wèn)題。最難搞的一種錯(cuò)誤
不要害怕遇到錯(cuò)誤,每出現(xiàn)一次的錯(cuò)誤就是一次突破自我的機(jī)會(huì)。
學(xué)會(huì)積累排錯(cuò)經(jīng)驗(yàn),勇于嘗試。不經(jīng)風(fēng)雨 ?? ,怎能見(jiàn)彩虹 ??
以上,就是本期的全部?jī)?nèi)容啦??
總結(jié)
到此這篇關(guān)于Visual Studio實(shí)用調(diào)試技巧大全的文章就介紹到這了,更多相關(guān)VS調(diào)試技巧內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
20個(gè)提高開(kāi)發(fā)效率的VS Code快捷鍵(推薦)
這篇文章主要給大家介紹了20個(gè)提高開(kāi)發(fā)效率的VS Code快捷鍵,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
從此不再懼怕URI編碼 JavaScript及C# URI編碼詳解
JavaScript中的還好,只提供了三個(gè),C#中主要用的就有這么多,還沒(méi)有列出其他編碼(HTML),一多就弄不明白,弄不明白就心生恐懼,心生恐懼就變得苦逼,本文就向大家詳細(xì)解釋在JavaScript及C#中如何對(duì)URI進(jìn)行編碼的方法(注:本文不涉及到其他編碼)。2012-03-03
Fiddler?Everywhere?4.0.1的破解及禁止更新的方法(最新推薦)
Fiddler?Everywhere是個(gè)好東西,無(wú)論抓包還是調(diào)試接口都很好用,只是現(xiàn)在收費(fèi)了,為了白嫖就決定折騰一下,如果一開(kāi)始通過(guò)設(shè)置文件夾權(quán)限禁止更新,也會(huì)導(dǎo)致進(jìn)不了使用界面而無(wú)法使用,下面說(shuō)一下具體的解決過(guò)程2023-11-11
深入解析Git中rebase與merge的核心區(qū)別及最佳實(shí)踐
在日常開(kāi)發(fā)中,Git 是不可或缺的版本控制工具,本文將深入解析Git中rebase與merge的核心區(qū)別及最佳實(shí)踐,從而帶你徹底搞懂這兩個(gè)命令,并學(xué)會(huì)如何科學(xué)地管理 Git 分支2026-05-05
Unity項(xiàng)目?jī)?yōu)化相關(guān)技巧
隨著項(xiàng)目越做越大,工作年限的增加,對(duì)項(xiàng)目的優(yōu)化方面要求也越來(lái)越高(面試必備),本文簡(jiǎn)單羅列一些unity項(xiàng)目中的優(yōu)化技巧,有需要的朋友可以參考下2021-09-09

