首頁 > 軟體

優秀程式猿偵錯技巧Debug與Release

2022-02-16 10:00:16

Bug

bug意為臭蟲,計算機術語裡就是么蛾子,對,你的程式又出么蛾子了。為什麼要叫bug?關於這個還有段有趣的歷史

有一天赫柏正愉快地敲著Mark Ⅱ的程式碼時,計算機突然就停止運作了,那時的計算機遠不如現在小巧,赫柏他們只能一個個排查計算機龐大的處理器群,經過一段時間的排查後停機原因終於被找到了。原來是一隻飛蛾被計算機的光和熱吸引,觸發了電腦的短路,當然這隻可憐的飛蛾也一命嗚呼了按理說一般人也就是把飛蛾拿走,然後重啟下電腦也就完事了,但赫柏顯然不是一般人她小心翼翼地把這隻飛蛾拿了下來,然後把它工工整整地粘在了記事本上… …

這就是歷史上第一個 bug 的誕生。

偵錯的重要性

我估計前期我們找 bug 都是用眼睛瞅,特別是我們這種大一的剛接觸的,現在還好,到了以後需要寫大工程的時候,眼瞅不頭疼的才是大哥,對於一個成熟程式設計師20%時間寫程式碼而80%時間在偵錯程式碼。
我們寫程式碼就是一個推理的過程,整個流程的正確與錯誤都是有跡可循的,推理的途徑就是這些跡象。
一名優秀的程式設計師就是一個優秀的偵探,找到跡象,順流而下是錯誤,順流而上是真相,偵錯就是我們破案的過程。

偵錯基本步驟

1.找錯(進行隔離,消除來定位錯誤)

2.知道錯因

3.尋找解決辦法

4.糾正,重測

Debug與Release

Debug(Debugging),即排錯,稱為偵錯版本,不作任何優化,包含偵錯資訊,便於我們偵錯程式。

Release ,即釋放,成為測試版本,往往進行各種優化,讓程式碼在大小和執行速度上都是最優的,面向使用者,可以很好的使用。但是!注意Release版本是沒法進行偵錯的,這種觀點僅限於我當前知識面的限制,實際上Release也是可以的,下面是大佬對我的指正意見:

快捷鍵

在偵錯過程中,掌握一些快捷鍵會大大增加我們的效率。以vs2019為例,我們先會設定斷點如圖(行標左側設定)

斷點設定在需檢查程式碼的任意位置,執行到這一步就會停下給我們報告。斷點完F5偵錯,執行視窗彈出後就會發現偵錯就會出現更多內容,我框出來的在之前記錄C語言學習時都有用到。

注意F5是偵錯,Ctrl+F5是執行,通常會使用會F5跳到想要的斷點處,有些電腦上比較裝怪,快捷鍵沒反應的,建議多按一個Fn鍵試試,Fn是功能輔助鍵,相當於一個開關,本質上 F5+Fn = F5。需要強調的是逐語句和逐過程,如果你想看每個細節,不放過每一個角落就用逐語句,兩者的力度是不一樣的,逐過程會跳過程式碼裡的函數部分。
vs玩家重點推薦 Ctrl+k+c,註釋選中行;Ctrl+k+u,取消註釋,熟練運用會很方便。

其餘還有很多不贅述,下面準備了超全的實用快捷鍵用法:

超全整理visual studio快捷鍵使用技巧

除了用偵錯驗證程式碼的正確性,還可以用於研究具體的問題,舉個栗子:
這道題是Nice的面試真題

請說明下面程式碼是否能正常運?執行結果是什麼?為什麼會出現這個結果?

int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf(「hehen」);
}
return 0;
}

這裡當我們開啟偵錯視窗直接開調:

很直觀的可以發現,哦,原來是i與arr[12]相同,我們發現在arr[12]改變時i第的值也會隨之改變,那我就直接取出他們都地址看一看是不是一樣的。

OMG,是一樣的。但我們這裡的偵錯只能看到現象,他底層的原理我們要自己思考。

其死迴圈的邏輯大致是這樣的,我們建立了一個變數i,arr,他們都是區域性變數,而區域性變數時放在棧上的,棧區上記憶體使用習慣是先使用高地址儲存空間,再使用低地址。這裡注意,我們開始給的十個大小的空間,i的變數是到12,這裡明顯是越界存取,但為什麼沒有報錯停下來?結合我們剛剛監視的結果,我們再把格局開啟:

陣列隨著下標的增長,地址是由低到高的變化,在我陣列適當越界時,如果i和arr之間的空間適當的話,就有可能使arr向後越界時就存取到了i,造成了迴圈變數的i改變,最終會死迴圈。

這種錯誤其實存在偶然性,首先i和arr[12]相同,只是恰巧,但如果我把i換成11,結果就大相徑庭了,我只形成了越界但沒有改變回圈變數i的值。其次,這個程式碼是嚴重依賴環境的,比如在VC 6.0裡面i和arr就是連續的,gcc裡面i和arr之間有一個空間。

打趣的是,我們在Release版本里面是不會報錯並且會停下來,其實在剛剛的截圖裡面也是會報錯的,但是!死迴圈停不下來,他根本沒時間來報錯。Rlease的優化並不是萬能的,不要期待利用Release版本來掩蓋程式碼的bug,最好的做法就是不要越界。

如何寫出易於偵錯(優秀)的程式碼

1.硬性要求執行正常

2.bug少(估計沒人敢保證零bug吧)

3.效率高

4.可讀性高

5.可維護性(容易修改與二創)

6.註釋(方便閱讀)

7.檔案齊全

常見的coding技巧

1.使用assert

意為斷言,在程式碼執行前設的前哨,比如我們函數傳參時,當我傳的內容變成空指標,後面如果函數進行解除參照操作,對於空指標解除參照是會造成程式崩潰的,是很危險的,所以我們用assert當監護人能快一步該訴我們問題在這裡;設定assert也是一個好習慣,面試官見了直呼老司機!

# include<assert.h>
void my_str(char* a, char* b)
{
	assert(a != NULL && b != NULL);//斷言
	while (*b != '')
	{
		*a = *b;
		b++;
		a++;
	}
	*a = *b;
}
int main()
{
	char arr[10] = {0};
	char arr2[] = "bit";
	my_str(NULL, arr2); // 故意設成NULL程式會崩潰
	printf("%sn", arr);

	return 0;
}

效果如上圖就會顯示斷言失敗。

2.儘量使用const

const常變數修飾符。

就上面模擬strcpy函數,如果有天有個內鬼改了你的程式碼,寫成 *b = *a,就拷反了,編譯器也會傻不拉嘰的輸出,儘管結果什麼都沒有。那怎麼辦呢?我在開頭就定義好

char* my_str(const char *a,const char* b)

這樣不管你咋改,我 *a,*b都是無法改變的。

注意const int *p=&a , const 在 * 左邊時,修飾的是指標指向的內容,指標變數不影響 ;在 * 右邊是,修飾指標本身,指標變數不能修改,其內容可以通過指標來改變。

3,形成良好的編碼風格

4.註釋!註釋!註釋!(好習慣講三次)

5.避免編碼陷阱

今天就到這裡了,摸了家人們,更多關於Debug與Release偵錯技巧的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com