目錄
一、什么是bug?
二、調試
1.一般調試的步驟
2.Debug 和 Release
三、調試環境準備
?四、調試時要查看的信息
1.查看臨時變量的值
2.查看內存信息?
?3.查看調用堆棧
?4.查看反匯編信息
5.查看寄存器?
五、練習
六、常見的coding技巧
七、const的作用?
八、編程常見的錯誤
一、什么是bug?
我們平時會口頭說 bug ,報錯,waring(報警)等,bug 英文的意思是蟲子,然而在計算機發展史上的第一只 Bug ,真的是因為一只飛蛾意外走入一電腦而引致故障,因此Bug從原意為臭蟲引申為程序錯誤。
當我們
?
?這個時候就需要我們的調試 來開啟新大陸
關于程序錯誤的?參考資料
二、調試
平時敲代碼,總會遇到與一些問題導致程序執行不過去,你可能在那一直盯著剛寫完的代碼看(心里想這到底哪里出錯了,但是就是沒有找打錯誤的原因),這時就需要我們平時了解到的調試來解決問題(起先使用可能不熟練,慢慢來)
調試(英語:Debugging / Debug),又稱除錯,是發現和減少計算機程序或電子儀器設備中程序錯誤的一個過程
1.一般調試的步驟
- 發現程序錯誤的存在
- 以隔離、消除等方式對錯誤進行定位
- 確定錯誤產生的原因
- 提出糾正錯誤的解決辦法
- 對程序錯誤予以改正,重新測試
2.Debug 和 Release
Debug 通常稱為調試版本,它包含調試信息,并且不作任何優化,便于程序員調試程序。
Release 稱為發布版本,它往往是進行了各種優化,使得程序在代碼大小和運行速度上都是最優的,以便用戶很好地使用。
接下來調試下方代碼
#include<stdio.h>
int main()
{char* p = "hello word!";printf("%s\n",p);return 0;
}
在debug版本下 (執行程序)文件名.exe? 是幾十KB
而在release版本下? 是 幾 KB(原因是代碼大小和運行速度上都是最優的)
再看下方代碼
#include<stdio.h>
int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i <= 12;i++){arr[i] = 0;printf("haha\n");}return 0;
}
在 vs2022 x86 debug 的環境下?
該程序的【執行結果】 無限循環打印 haha
而在release版本下?
?
沒有死循環 打印了13行的haha
二者區別是因為:變量在內存中開辟的順序發生了變化,影響到了程序執行的結果
三、調試環境準備
?如果要對代碼進行調試首先要準備好調試的環境
就是要在debug版本下,才能使代碼正常調試
(點擊開始調試)或者按F5
在這里介紹一些調試的快捷鍵
- F5? 啟動調試,經常用來直接跳到下一個斷點處?
- F9??創建斷點和取消斷點。?斷點的重要作用,可以在程序的任意位置設置斷點。
這樣就可以使得程序在想要的位置隨意停止執行,繼而一步步執行下去 - F11? 逐語句,就是每次都執行一條語句,但是這個快捷鍵可以使我們的執行邏輯進入函數內部(這是最長用的)
- F10??逐過程,通常用來處理一個過程,一個過程可以是一次函數調用,或者是一條語句
- Ctrl + F5?開始執行不調試,如果你想讓程序直接運行起來而不調試就可以直接使用
其他快捷鍵
?四、調試時要查看的信息
1.查看臨時變量的值
在按調試后,觀察變量的值
例如 輸入 i
?
一直按F11當 i 的值變為 11時? i值的變化(0-11)
2.查看內存信息?
?
在內存窗口 輸入 &i(找到i 的內存地址)
?3.查看調用堆棧
反映的是調用邏輯
?4.查看反匯編信息
?
5.查看寄存器?
五、練習
【例 1】
//實現代碼:求 1!+2!+3! ...+ n! ;不考慮溢出
int main()
{int i = 0;int sum = 0;//保存最終結果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;
}
?輸入 1,輸入2 和我們預想的結果一樣,但當我們輸入 3 的時候結果應該是 9 實際輸出結果為:
?打印的結果出錯了
接著進行調試,當調試到 i= 2是 正常的
?調試到 j = 3 是 ret 應該是 6 ,但是發現 ret由4 變到 12
?經果分析我們發現 原來是ret 每次進入內層的for循環 ret 的值接著上次的執行結果繼續算
這時 我們在內層for循環上方加上? ret? =1;
//實現代碼:求 1!+2!+3! ...+ n! ;不考慮溢出
#include<stdio.h>
int main()
{int i = 0;int sum = 0;//保存最終結果int n = 0;int ret = 1;//保存n的階乘scanf("%d", &n);for (i = 1; i <= n; i++){int j = 0;ret = 1;//添加的代碼for (j = 1; j <= i; j++){ret *= j;}sum += ret;}printf("%d\n", sum);return 0;
}
?【例 2 】死循環的原因
#include<stdio.h>
int main()
{int i = 0;int arr[10] = { 0 };for (i = 0; i <= 12;i++){arr[i] = 0;printf("haha\n");}return 0;
}
調試后發現?
?
六、常見的coding技巧
- 使用assert(斷言,是一個宏,在release版本中會自動優化掉)
- 盡量使用const(下面會講到用法)
- ?養成良好的編碼風格
- 添加必要的注釋
- 避免編碼的陷阱
【例】模擬實現庫函數strcpy、
庫函數strcpy?
//模擬實現strcpy
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char *des,const char *src)
{assert(des != NULL);assert(src != NULL);//避免字符串為空char* temp = des;while (*des){*des = *src;des++;src++;}return (temp);
}
int main()
{char* str = "ab";char arr[20] = "xxxxxxxxxx";printf("%s\n",my_strcpy(arr,str));return 0;
}
優化
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* des,const char *src)
{assert(des != NULL);assert(src != NULL);char* temp = des;//用于返回首元素地址while (*temp++ = *src++);return des;
}
int main()
{char *arr1 = "abcdef";char* arr2[20] = {0};printf("%s\n",my_strcpy(arr2,arr1));return 0;
}
七、const的作用?
const 在 * 左邊
int num =0;
int n = 0;
const int *p =#
p = &n; //ok
*p = 20; //error
const 在 * 右邊
int n = 1000;
int num = 0;
int * const p = # //限制了指針變量本身
p = &n; //error
*p = 20;//ok
?【小總結】
const 修飾指針變量的時候:
- const放在 * 左邊,修飾的是指針指向的內容,保證指針指向的內容不被修改。但是指針變量可以修改
- const 放在* 右邊,修飾的是指針變量本身,保證指針變量本身不被修改。但是可以修改指針指向的內容
練習:模擬實現strlen
//模擬實現strlen
#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{assert(str != NULL);int count = 0;while (*str) {count++;str++;}return count;
}
int main()
{char* str = "abcdefg";printf("%d\n",my_strlen(str));return 0;
}
八、編程常見的錯誤
-
編譯型錯誤
直接看錯誤提示信息(雙擊),解決問題。或者憑借經驗就可以搞定
- 鏈接型錯誤
看錯誤提示信息,主要在代碼中找到錯誤信息中的標識符,然后定位問題所在。一般是標識符名不
存在或者拼寫錯誤
- 運行時錯誤
借助調試,逐步定位問題。