调试试炼开始
調試修煉
- 前言
- 1. bug的誕生
- 2. 調試 - Debug
- 2.1 調試是什么
- 2.2 基本步驟
- 2.3 了解Debug和Release
- 2.3.1 區別
- 2.3.2 例子 - Visual Studio 2019 為例
- 2.3.2.1 在空間大小上的差異:
- 2.3.2.2 在具體調試上的差異:
- 2.3.2.3 一個具體的例子
- 3. windows環境調試 -
- 3.1 調試準備 - Visual Studio 2019
- 3.2 調試相關快捷鍵
- 3.3 調試下的各種信息的查看
- 4. 寫出優秀的代碼
- 4.1 優秀的代碼
- 4.2 const的修飾作用
- 4.3 一個例子 - 模擬實現strlen()
- 5. 編程常見的錯誤
- 5.1 編譯錯誤
- 5.2 鏈接錯誤
- 5.3 運行錯誤
- 結束語
前言
調試是一個程序員所要必備的技能,我們再遇到程序編譯器無法發現的問題時要能夠通過調試一步一步的來找到問題所在。
1. bug的誕生
bug原意指蟲子,有一天小飛蛾意外飛進了正在工作的計算機電路里導致了計算機工作發生故障,工作人員對當時的計算機進行了細致的檢查后最終發現了這只被夾扁的飛蛾,之后計算機便恢復了正常工作狀態。這只飛蛾順手被夾在了格蕾絲-霍普的工作筆記里并備注為bug,bug便誕生了。
2. 調試 - Debug
有了bug就必須要找出這個bug,這個操作過程叫做調試debug/debugging。
調試就像推理,從迷霧中尋找真相。
2.1 調試是什么
調試是發現和減少計算機程序或電子儀器設備中程序錯誤的一個過程。
2.2 基本步驟
- 發現錯誤
- 錯誤定位 - 隔離、消除等
- 確定原因
- 提出方法
- 改正錯誤
- 再次測試
2.3 了解Debug和Release
2.3.1 區別
Debug稱為調試版本,包含調試信息,不做任何優化,以便于程序員進行調試。
Release稱為發布版本,不包含調試信息,進行了各種優化,程序在代碼大小和運行速度上都是最優的,以便于用戶使用。
相比調試版本,發布版本重點優化了體積大小與性能效率兩方面。
不是所有程序都能進行調試,包含調試信息的程序才能進行調試。
2.3.2 例子 - Visual Studio 2019 為例
對于同一個程序分別在Debug和Release版本下的一些差異
#include <stdio.h>int main() {printf("Hello World!\n");return 0; }2.3.2.1 在空間大小上的差異:
2.3.2.2 在具體調試上的差異:
#include <stdio.h>int main() {int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++) {arr[i] = i;}return 0; }Debug版本下:可以進行調試
Release版本下:不可進行調試,程序直接執行完畢
2.3.2.3 一個具體的例子
注意變量i與數組arr建立先后關系的差異導致程序運行的不同。
#include <stdio.h>int main() {int i = 0;int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };for(i=0; i<=12; i++){arr[i] = 0;printf("hello!\n");}return 0; }Debug版本下:陷入死循環
Release版本下:
Debug版本下:
程序崩潰,
Release版本下:
3. windows環境調試 -
3.1 調試準備 - Visual Studio 2019
選擇Debug模式才能進行調試。
3.2 調試相關快捷鍵
F5
**啟動調試,遇到斷點時停下,如果沒有斷點就直接完整執行程序。 **
如果有多個斷點,按下F5可以使程序從當前斷點直接運行到下一個邏輯上的斷點。(注意邏輯斷點與實際斷點可能并不一定完全等價,例如斷點設置在一個循環的內部時,邏輯斷點是下一次循環的斷點,但可能實際的斷點位置不變)。
ctrl + F5
開始執行但不調試。直接運行程序,如果程序沒有編譯鏈接過,該操作還會進行新程序的編譯與鏈接。
F9
在某一行設置斷點或者取消某一行已有的斷點。
可以在程序的任意位置設置斷點,但在空語句處的斷點沒有意義。
斷點可以使程序在我們預期停止的地方停下來.
F10
逐過程調試,程序停在main函數入口處,可以通過多次按F10來使程序在可觀察的狀態運行。通常用來處理一個過程,一個過程可以是一次函數調用、一條語句等。
F11
逐語句調試,每次都只執行一條語句,使用F11可以進入到用戶自定義函數的內部,比F10更加細致(因為F10并不能進入用戶自定義函數內部)。
3.3 調試下的各種信息的查看
自動窗口
不需要手動輸入,隨著調試的進行程序中變量、數組等信息會自動顯示相關信息,注意自動窗口顯示的是調試附近的相關信息,距離較遠的已經調試過得或未調試的都不會再顯示,也就是說信息顯示是不固定的,觀察起來并不方便。
監視
需要手動輸入想知道的信息,只有手動刪除輸入的信息時才會刪除。信息的顯示是固定的,方便觀察。
內存
查看程序中各數據在內存中的信息。
調用堆棧
調用堆棧,主要是程序有多個函數并且存在嵌套調用時可以觀察到函數的調用關系和當前調用所處的位置。
反匯編
查看程序的匯編代碼,更加底層。
寄存器
寄存器是CPU內部用來存放數據的一些小型儲存區域,用來暫時存放參與運算的數據和運算結果,有著非常高的讀寫速度。可以觀察到當前運行情況下寄存器的使用信息。
4. 寫出優秀的代碼
4.1 優秀的代碼
便于調試
運行良好
效率高
可讀性高
可維護性高
注釋清晰
文檔齊全
寫代碼時的一些技巧:
先思考再動手
使用assert(斷言)
使用const
有良好的代碼風格
注意寫上應有的注釋
4.2 const的修飾作用
const修飾可以減小權限,使程序更加安全。
對于普通變量const修飾后普通變量不能直接被修改,否則程序出錯。但是可以通過對變量的地址解引用修改變量的值。
對于指針變量有兩種情況:
const在*左邊,此時const修飾的是指針所指向的對象,而不是指針本身。不能通過指針解引用的方式改變指針所指向的對象,但可以不通過指針而直接修改那個對象。
const在*右邊,此時const修飾的是指針本身。指針獲得一個變量的地址后不能在被另一個地址賦值。
#include <stdio.h>int main(){int a = 10;int b = 10;int c = 10;int d = 10;//無限制的指針變量p1int *p1 = &a;//指針指向對象受到限制的指針p2const int *p2 = &b;//int const *p2 = &a;a = 5;//true//指針本身受到限制的指針p3int* const p3 = &c;p3 = &a;//error//指針本身和指針指向對象都受到限制的指針p4const int* const p4 = &d;*p4 = 5;//errord = 5;//truep4 = &a;//errorreturn 0; }4.3 一個例子 - 模擬實現strlen()
#include <stdio.h> #include <assert.h>//使用const修飾指針str所指向的對象,使其不能被更改,否則程序出錯 int my_strlen(const char *str){//傳入的指針可能是空指針NULL,此時str就是野指針,沒有指向對象,會導致程序出現錯誤assert(str != NULL);int cnt = 0;while(*str != NULL){str++;cnt++;}return cnt; } int main(){char str[] = "abcdef";int len = my_strlen();printf("%d\n", len);return 0; }5. 編程常見的錯誤
從一個代碼文件(源文件)經過編譯、鏈接過程到得到可執行程序
5.1 編譯錯誤
在編譯期間出現的錯誤,編譯器一般會給出對應錯誤的相關位置代碼行,是語法方面的錯誤,相對簡單。
5.2 鏈接錯誤
在鏈接期間出現的錯誤,鏈接器把包括源文件在內的多個文件(如頭文件)鏈接在一起形成一個可執行文件。不是語法錯誤,一般是代碼中出現了未定義的函數等外部符號,鏈接錯誤一般不給出錯誤出現的代碼行,但會標識除未定義的符號,可以使用查找功能進行排查。
#include <stdio.h> void print(){printf(" world\n") } int main(){printf("hello");test();//該函數未定義;Print();//該函數雖然定義了,但定義的函數名與引用的函數名不匹配return 0; }5.3 運行錯誤
邏輯錯誤等,需要進行調試找出錯誤所在,最不好找!。
結束語
調試技能是程序員所要必備的技能,隨著項目代碼量的增加,調試尋找問題也就顯得更加重要。不同編譯器調試功能可能會有不同,但調試的方法是相同的。
END
總結
- 上一篇: ZXing.Net条形码二维码标签编辑打
- 下一篇: 阿里云被攻击封多久,又该怎么解决?