?
- ??博客主頁:江池俊的博客
- ??專欄推薦:?C語言初階之路 ?C語言進(jìn)階之路 ?數(shù)據(jù)結(jié)構(gòu)探索
- ??代碼倉庫:江池俊的代碼倉庫
- ??? 社區(qū):GeekHub社區(qū) ?
- ??歡迎大家點(diǎn)贊??評(píng)論??收藏?
- ?? 如果覺得博主的文章還不錯(cuò)的話,請(qǐng)點(diǎn)贊??收藏?? 三連支持一下博主??
一、 什么是bug?
在計(jì)算機(jī)編程領(lǐng)域,bug指的是程序中存在的錯(cuò)誤或缺陷。當(dāng)程序無法按照預(yù)期的方式運(yùn)行,或者產(chǎn)生意料之外的結(jié)果時(shí),通常會(huì)被認(rèn)為是有bug。bug可以導(dǎo)致程序崩潰、產(chǎn)生錯(cuò)誤的輸出、不正確的行為或不一致性。bug可以是由代碼錯(cuò)誤、邏輯錯(cuò)誤、算法問題、輸入錯(cuò)誤、外部環(huán)境因素等引起的。發(fā)現(xiàn)和修復(fù)bug是軟件開發(fā)中的重要環(huán)節(jié),通常需要進(jìn)行調(diào)試和測試來定位和解決問題。
第一次被發(fā)現(xiàn)的導(dǎo)致計(jì)算機(jī)錯(cuò)誤的飛蛾,也是第一個(gè)計(jì)算機(jī)程序錯(cuò)誤。
注:參考鏈接
二、調(diào)試是什么?有多重要?
調(diào)試(英語:Debugging / Debug):又稱除錯(cuò),是發(fā)現(xiàn)和減少計(jì)算機(jī)程序或電子儀器設(shè)備中程序錯(cuò)誤的一個(gè)過程。
?? 調(diào)試的基本步驟
- 發(fā)現(xiàn)程序錯(cuò)誤的存在
- 以隔離、消除等方式對(duì)錯(cuò)誤進(jìn)行定位
- 確定錯(cuò)誤產(chǎn)生的原因
- 提出糾正錯(cuò)誤的解決辦法
- 對(duì)程序錯(cuò)誤予以改正,重新測試
三、Debug和Release版本的介紹。
Debug 通常稱為調(diào)試版本,它包含調(diào)試信息,并且不作任何優(yōu)化,便于程序員調(diào)試程序。
Release 稱為發(fā)布版本,它往往是進(jìn)行了各種優(yōu)化,使得程序在代碼大小和運(yùn)行速度上都是最優(yōu)的,以便用戶很好地使用。
代碼:
#include <stdio.h>
int main()
{
char *p = "hello world.";
printf("%s\n", p);
return 0;
}
上述代碼在Debug環(huán)境的結(jié)果展示:
上述代碼在Release環(huán)境的結(jié)果展示:
Debug和Release反匯編展示對(duì)比:
所以我們說調(diào)試就是在Debug版本的環(huán)境中,找代碼中潛伏的問題的一個(gè)過程。
四、Windows環(huán)境調(diào)試介紹
- Debug為調(diào)試版本,一般在開發(fā)完成后發(fā)布工程前,調(diào)試代碼都是在Debug模式下進(jìn)行的。
- Release版本是不能調(diào)試的,一般都是在Debug版本下調(diào)試的,Release版本一般編譯器會(huì)進(jìn)行大量的優(yōu)化,刪除無用的代碼,指令的次序調(diào)整等,讓其速度更快。
在環(huán)境中選擇 debug 選項(xiàng),才能使代碼正常調(diào)試。
??常用快捷鍵
- F5 -->
啟動(dòng)調(diào)試
,經(jīng)常用來直接跳到下一個(gè)斷點(diǎn)處。一般不會(huì)單獨(dú)使用,而是配合F9一起使用。 - F9 -->
創(chuàng)建斷點(diǎn)和取消斷點(diǎn)
。斷點(diǎn)的重要作用:可以在程序的任意位置設(shè)置斷點(diǎn)。這樣就可以使得程序在想要的位置隨意停止執(zhí)行,繼而一步步執(zhí)行下去。 - F10 --> 逐過程,通常用來處理一個(gè)過程,一個(gè)過程可以是一次函數(shù)調(diào)用,或者是一條語句。(遇到函數(shù)直接執(zhí)行完整個(gè)函數(shù),不進(jìn)入函數(shù)內(nèi)部)
- F11 --> 逐語句,就是每次都執(zhí)行一條語句,但是這個(gè)快捷鍵可以使我們的執(zhí)行邏輯
進(jìn)入函數(shù)內(nèi)部(這是最 常用的)
。 - CTRL + F5 --> 開始執(zhí)行不調(diào)試,如果你想讓程序直接運(yùn)行起來而不調(diào)試就可以直接使用。
想知道更多快捷鍵?點(diǎn)我
??調(diào)試的時(shí)候查看程序當(dāng)前信息
1. 查看臨時(shí)變量的值:在調(diào)試開始之后,用于觀察變量的值。
監(jiān)視窗口:
2. 查看內(nèi)存信息:在調(diào)試開始之后,用于觀察內(nèi)存信息。
內(nèi)存窗口:
3. 查看調(diào)用堆棧:通過調(diào)用堆棧,可以清晰的反應(yīng)函數(shù)的調(diào)用關(guān)系以及當(dāng)前調(diào)用所處的位置。
調(diào)用堆棧窗口:
4. 查看匯編信息:在調(diào)試開始之后,有兩種方式轉(zhuǎn)到匯編:
(1)第一種方式:右擊鼠標(biāo),選擇【轉(zhuǎn)到反匯編】:
(2)第二種方式:調(diào)試窗口找到反匯編
5. 查看寄存器信息:可以查看當(dāng)前運(yùn)行環(huán)境的寄存器的使用信息。
多多動(dòng)手,嘗試調(diào)試,才能有進(jìn)步。
一定要熟練掌握調(diào)試技巧, 初學(xué)者可能80%的時(shí)間在寫代碼,20%的時(shí)間在調(diào)試。但是一個(gè)程序員可能20%的時(shí)間在寫 程序,但是80%的時(shí)間在調(diào)試。
五、調(diào)試實(shí)例
?? 實(shí)例1
實(shí)現(xiàn)代碼:求 1!+2!+3! …+ n! ;不考慮溢出。代碼改正:
#include<stdio.h>
int main()
{
int i = 0;
int sum = 0;//保存最終結(jié)果
int n = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
int ret = 1;//保存n的階乘
int j = 0;
for (j = 1; j <= i; j++)
{
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
?? 實(shí)例2
注意:此代碼非常依賴環(huán)境,在vs2022 debug版本x86環(huán)境下才是死循環(huán)的,而release版本下此代碼會(huì)被優(yōu)化,改變內(nèi)存布局,不會(huì)死循環(huán)。
六、如何寫出好(易于調(diào)試)的代碼
優(yōu)秀代碼的特點(diǎn):
- 代碼運(yùn)行正常
- bug很少
- 效率高
- 可讀性高
- 可維護(hù)性高
- 注釋清晰
- 文檔齊全
常見的coding技巧:
- 使用assert
- 盡量使用const
- 養(yǎng)成良好的編碼風(fēng)格
- 添加必要的注釋
- 避免編碼的陷阱。
??assert的使用
assert 函數(shù)是一種在代碼中用于測試和調(diào)試的工具,它用于在運(yùn)行時(shí)檢查一個(gè)條件是否為真。如果條件為真,則 assert 什么也不做,程序繼續(xù)執(zhí)行。但如果條件為假,assert 會(huì)引發(fā)一個(gè)異常(通常是 AssertionError),中斷程序的執(zhí)行。
示例:
模擬實(shí)現(xiàn)庫函數(shù):strcpy
/*
庫函數(shù)strcpy
1.描述
C 庫函數(shù) char *strcpy(char *dest, const char *src) 把 src 所指向的字符串復(fù)制到 dest。
需要注意的是如果目標(biāo)數(shù)組 dest 不夠大,而源字符串的長度又太長,可能會(huì)造成緩沖溢出的情況。
2.聲明
下面是 strcpy() 函數(shù)的聲明。
char *strcpy(char *dest, const char *src)
3.參數(shù)
dest -- 指向用于存儲(chǔ)復(fù)制內(nèi)容的目標(biāo)數(shù)組。
src -- 要復(fù)制的字符串。
4.返回值
該函數(shù)返回一個(gè)指向最終的目標(biāo)字符串 dest 的指針。 */
char *my_strcpy(char * dst, const char * src)
{
char * cp = dst;
assert(dst && src);//斷言,防止傳進(jìn)來的是空指針或野指針
while( *cp++ = *src++ )
{
;
} /* Copy src over dst */
return dst;
}
??空指針和野指針的危害
1.空指針(Null Pointer):
空指針是指不指向任何有效內(nèi)存位置的指針,通常用空值(NULL)表示。空指針通常表示指針尚未初始化或不引用任何有效的內(nèi)存。訪問空指針通常會(huì)導(dǎo)致程序崩潰或未定義的行為。主要危害有:
- 程序崩潰:訪問空指針可能會(huì)導(dǎo)致程序直接崩潰,因?yàn)椴僮飨到y(tǒng)會(huì)捕獲到這種無效的內(nèi)存訪問并終止程序。
- 未定義行為:C語言標(biāo)準(zhǔn)規(guī)定對(duì)空指針的解引用是未定義行為,這意味著不同的編譯器和平臺(tái)可能會(huì)表現(xiàn)出不同的行為,包括奇怪的運(yùn)行時(shí)行為和數(shù)據(jù)損壞。
- 安全問題:攻擊者可以利用空指針漏洞來執(zhí)行惡意代碼,從而造成系統(tǒng)的安全問題。
2.野指針(Dangling Pointer):
野指針是指在指針指向的內(nèi)存位置被釋放或無效后,仍然保持了該指針的值。在訪問野指針時(shí),可能會(huì)讀取到無效的數(shù)據(jù)或者修改其他內(nèi)存區(qū)域,導(dǎo)致未定義的行為。主要危害有:
- 數(shù)據(jù)損壞: 野指針可能會(huì)導(dǎo)致數(shù)據(jù)損壞,因?yàn)槌绦蚩赡軙?huì)誤用已經(jīng)釋放或者不再有效的內(nèi)存位置。
- 難以調(diào)試: 由于野指針可能導(dǎo)致未定義行為,程序可能會(huì)表現(xiàn)出奇怪的錯(cuò)誤,這會(huì)使調(diào)試變得非常困難。
??const的作用
const修飾指針變量的時(shí)候:
1. const如果放在*的左邊,修飾的是指針指向的內(nèi)容,保證指針指向的內(nèi)容不能通過指針來改變。但是指針變量本身的內(nèi)容可變。
#include <stdio.h>
int main() {
int num = 5;
const int* ptr = # // const在*的左邊,指針本身可以變,但是指向的內(nèi)容不可變
// *ptr = 10; // 這里會(huì)產(chǎn)生編譯錯(cuò)誤,因?yàn)椴荒芡ㄟ^ptr修改num的值
num = 10; // 可以通過num直接修改值
printf("num: %d\n", num); // 輸出:num: 10
int another_num = 20;
ptr = &another_num; // 可以將ptr指向另一個(gè)整數(shù)
return 0;
}
在上述示例中,ptr 是一個(gè)指向 const int 的指針,這意味著不能通過 ptr 來修改它指向的內(nèi)容,但可以通過修改 num 的值來間接地修改指針?biāo)赶虻膬?nèi)容。另外,可以改變 ptr 指向其他整數(shù)。
2. const如果放在*的右邊,修飾的是指針變量本身,保證了指針變量的內(nèi)容不能修改,但是指針指向的內(nèi)容,可以通過指針改變。
#include <stdio.h>
int main() {
int num = 5;
int* const ptr = # // const在*的右邊,指針本身不可變,但是指向的內(nèi)容可以改變
*ptr = 10; // 可以通過ptr修改num的值
printf("num: %d\n", num); // 輸出:num: 10
// 以下操作是不允許的,因?yàn)閜tr已經(jīng)被聲明為const,不能指向其他內(nèi)存
//int another_num = 20;
// ptr = &another_num; // 編譯錯(cuò)誤
return 0;
}
在上述示例中,ptr 是一個(gè)指向 num 的常量指針,這意味著不能通過 ptr 來改變它指向的位置,但可以通過 *ptr 來修改它指向的內(nèi)容。
練習(xí):
模擬實(shí)現(xiàn)一個(gè)strlen函數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-634457.html
#include <stdio.h>
int my_strlen(const char *str)
{
int count = 0;
assert(str != NULL);//斷言,也可以寫成 assert(str);
while(*str)//判斷字符串是否結(jié)束
{
count++;
str++;
}
return count;
}
int main()
{
const char* p = "abcdef";
//測試
int len = my_strlen(p);
printf("len = %d\n", len);
return 0;
}
七、編程常見的錯(cuò)誤
- 編譯型錯(cuò)誤
- 產(chǎn)生原因:編譯型錯(cuò)誤是在編譯階段發(fā)生的錯(cuò)誤,通常是由于語法錯(cuò)誤、類型錯(cuò)誤、未定義的標(biāo)識(shí)符等造成的。編譯器無法正確解析代碼,因此無法生成可執(zhí)行文件。
- 解決方法:仔細(xì)檢查代碼,確保語法正確、類型匹配,并確保使用的標(biāo)識(shí)符在正確的作用域中定義。查看編譯器的錯(cuò)誤信息和警告,逐一修復(fù)問題。相對(duì)來說簡單。
- 鏈接型錯(cuò)誤
- 產(chǎn)生原因:鏈接型錯(cuò)誤發(fā)生在鏈接階段,當(dāng)編譯器嘗試將多個(gè)源文件組合成一個(gè)可執(zhí)行文件時(shí)。常見的鏈接錯(cuò)誤包括重復(fù)定義、未定義的符號(hào)等。
- 解決方法:確保不同源文件中的函數(shù)和變量只有一次定義,避免重復(fù)定義。如果遇到未定義的符號(hào)錯(cuò)誤,檢查是否缺少某個(gè)庫文件的鏈接,或者確保函數(shù)定義在正確的源文件中。一般是標(biāo)識(shí)符名不存在或者拼寫錯(cuò)誤。
- 運(yùn)行時(shí)錯(cuò)誤
- 產(chǎn)生原因:運(yùn)行時(shí)錯(cuò)誤發(fā)生在程序執(zhí)行階段,可能由于無效的內(nèi)存訪問、除以零、類型不匹配等引起。這些錯(cuò)誤可能導(dǎo)致程序崩潰、產(chǎn)生未定義行為或者不正確的結(jié)果。
- 解決方法:使用合理的錯(cuò)誤處理機(jī)制來捕獲和處理運(yùn)行時(shí)錯(cuò)誤。例如,對(duì)于可能導(dǎo)致除以零的情況,可以在執(zhí)行之前進(jìn)行條件檢查。使用異常處理機(jī)制(例如C++ 中的 try-catch)來捕獲異常情況并進(jìn)行適當(dāng)?shù)奶幚?。確保指針的正確初始化和檢查,以避免空指針或野指針問題。要習(xí)慣借助調(diào)試,逐步定位問題。最難搞。
??今天的分享就到這里,如果覺得博主的文章還不錯(cuò)的話,請(qǐng)??三連支持一下博主哦??
文章來源地址http://www.zghlxwxcb.cn/news/detail-634457.html
到了這里,關(guān)于Visual Studio實(shí)用調(diào)試技巧---讓你成為高端的程序員的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!