这5个bug我不信你没有写过
大家好,我是寫(xiě)代碼的籃球。
計(jì)算機(jī)專業(yè)的小伙伴,在學(xué)校期間一定學(xué)過(guò) C 語(yǔ)言。它是眾多高級(jí)語(yǔ)言的鼻祖,深入學(xué)習(xí)這門(mén)語(yǔ)言會(huì)對(duì)計(jì)算機(jī)原理、操作系統(tǒng)、內(nèi)存管理等等底層相關(guān)的知識(shí)會(huì)有更深入的了解,所以我在直播的時(shí)候,多次強(qiáng)調(diào)大家一定要好好學(xué)習(xí)這門(mén)語(yǔ)言。
但是,即使是最有經(jīng)驗(yàn)的程序員也會(huì)寫(xiě)出各種各樣的 Bug。本文就盤(pán)點(diǎn)一下學(xué)習(xí)或使用 C 語(yǔ)言過(guò)程中,非常容易出現(xiàn)的 5 個(gè) Bug,以及如何規(guī)避這些 Bug。
這篇文章主要面向初學(xué)者,老鳥(niǎo)可以忽略哈(其實(shí)不少老鳥(niǎo)依然還會(huì)犯這些低級(jí)錯(cuò)誤哦)~
1. 變量未初始化
當(dāng)程序啟動(dòng)時(shí),系統(tǒng)會(huì)給它自動(dòng)分配一塊內(nèi)存,程序可以用它來(lái)存儲(chǔ)數(shù)據(jù)。所以如果你在定義一個(gè)變量時(shí),在未初始化的情況下,它的值有可能是任意的。
但這也不是絕對(duì)的,有些環(huán)境就會(huì)在程序啟動(dòng)時(shí)自動(dòng)將內(nèi)存「清零」,因此每個(gè)變量默認(rèn)值都是零。考慮到可移植性,最好要將變量進(jìn)行初始化,這是一名合格軟件工程師應(yīng)該養(yǎng)成的好習(xí)慣。
我們來(lái)看下下面這個(gè)使用幾個(gè)變量和兩個(gè)數(shù)組的示例程序:
#include?<stdio.h> #include?<stdlib.h>int?main() {int?i,?j,?k;int?numbers[5];int?*array;puts("These?variables?are?not?initialized:");printf("??i?=?%d\n",?i);printf("??j?=?%d\n",?j);printf("??k?=?%d\n",?k);puts("This?array?is?not?initialized:");for?(i?=?0;?i?<?5;?i++)?{printf("??numbers[%d]?=?%d\n",?i,?numbers[i]);}puts("malloc?an?array?...");array?=?malloc(sizeof(int)?*?5);if?(array)?{puts("This?malloc'ed?array?is?not?initialized:");for?(i?=?0;?i?<?5;?i++)?{printf("??array[%d]?=?%d\n",?i,?array[i]);}free(array);}/*?done?*/puts("Ok");return?0; }這段程序沒(méi)有對(duì)變量進(jìn)行初始化,所以變量的值有可能是隨機(jī)的,不一定是零。在我的電腦上它的運(yùn)行結(jié)果如下 :
These?variables?are?not?initialized:i?=?0j?=?0k?=?32766 This?array?is?not?initialized:numbers[0]?=?0numbers[1]?=?0numbers[2]?=?4199024numbers[3]?=?0numbers[4]?=?0 malloc?an?array?... This?malloc'ed?array?is?not?initialized:array[0]?=?0array[1]?=?0array[2]?=?0array[3]?=?0array[4]?=?0 Ok從結(jié)果可以看出,i 和 j 的值剛好是 0,但 k 值為 32766。在 numbers 數(shù)組中,大多數(shù)元素也恰好是零,除了第三個(gè)(4199024)。
在不同的操作系統(tǒng)上編譯這段相同的程序,運(yùn)行的結(jié)果有可能又是不一樣的。所以千萬(wàn)不要覺(jué)得你的結(jié)果就是正確唯一的,一定要考慮可移植性。
例如,這是在 FreeDOS 上運(yùn)行的相同程序的結(jié)果:
These?variables?are?not?initialized:i?=?0j?=?1074k?=?3120 This?array?is?not?initialized:numbers[0]?=?3106numbers[1]?=?1224numbers[2]?=?784numbers[3]?=?2926numbers[4]?=?1224 malloc?an?array?... This?malloc'ed?array?is?not?initialized:array[0]?=?3136array[1]?=?3136array[2]?=?14499array[3]?=?-5886array[4]?=?219 Ok可以看出來(lái),運(yùn)行的結(jié)果跟上面幾乎是天差地別。所以,對(duì)變量進(jìn)行初始化將為你省去很多不必要的麻煩,也便于將來(lái)的調(diào)試。
2. 數(shù)組越界
在計(jì)算機(jī)世界里,都是從 0 開(kāi)始計(jì)數(shù),但總有人有意無(wú)意忘記這點(diǎn)。比如一個(gè)數(shù)組長(zhǎng)度為 10 ,想要獲取最后一個(gè)元素的值,總有人用 array[10] ……
別問(wèn),問(wèn)就是我寫(xiě)過(guò)……
新手朋友犯這種低級(jí)錯(cuò)誤特別多。我們來(lái)看下數(shù)組越界會(huì)發(fā)生什么。
#include?<stdio.h> #include?<stdlib.h>int?main() {int?i;int?numbers[5];int?*array;/*?test?1?*/puts("This?array?has?five?elements?(0?to?4)");/*?initalize?the?array?*/for?(i?=?0;?i?<?5;?i++)?{numbers[i]?=?i;}/*?oops,?this?goes?beyond?the?array?bounds:?*/for?(i?=?0;?i?<?10;?i++)?{printf("??numbers[%d]?=?%d\n",?i,?numbers[i]);}/*?test?2?*/puts("malloc?an?array?...");array?=?malloc(sizeof(int)?*?5);if?(array)?{puts("This?malloc'ed?array?also?has?five?elements?(0?to?4)");/*?initalize?the?array?*/for?(i?=?0;?i?<?5;?i++)?{array[i]?=?i;}/*?oops,?this?goes?beyond?the?array?bounds:?*/for?(i?=?0;?i?<?10;?i++)?{printf("??array[%d]?=?%d\n",?i,?array[i]);}free(array);}/*?done?*/puts("Ok");return?0; }請(qǐng)注意,程序初始化了數(shù)組 numbers 所有元素的值(0~4),但是越界讀取了第 0~9 元素的值。可以看出來(lái),前五個(gè)值是正確的,但之后鬼都不知道這些值會(huì)是什么:
This?array?has?five?elements?(0?to?4)numbers[0]?=?0numbers[1]?=?1numbers[2]?=?2numbers[3]?=?3numbers[4]?=?4numbers[5]?=?0numbers[6]?=?4198512numbers[7]?=?0numbers[8]?=?1326609712numbers[9]?=?32764 malloc?an?array?... This?malloc'ed?array?also?has?five?elements?(0?to?4)array[0]?=?0array[1]?=?1array[2]?=?2array[3]?=?3array[4]?=?4array[5]?=?0array[6]?=?133441array[7]?=?0array[8]?=?0array[9]?=?0 Ok所以大家在寫(xiě)代碼過(guò)程中,一定要知道數(shù)組的邊界。像這種數(shù)據(jù)讀取的還好,如果一旦對(duì)這些內(nèi)存進(jìn)行寫(xiě)操作,直接就 core dump !
3. 字符串溢出
在 C 編程語(yǔ)言中,字符串是一組 char 值,也可以將其視為數(shù)組。因此,你也需要避免超出字符串的范圍。如果超出,則稱為字符串溢出。
為了測(cè)試字符串溢出,一種簡(jiǎn)單方法是使用 gets 函數(shù)讀取數(shù)據(jù)。gets 函數(shù)非常危險(xiǎn),因?yàn)樗恢澜邮账淖址锌梢源鎯?chǔ)多少數(shù)據(jù),只會(huì)天真地從用戶那里讀取數(shù)據(jù)。
如果用戶輸入字符串比較短那很好,但如果用戶輸入的值超過(guò)接收字符串的長(zhǎng)度,則可能是災(zāi)難性的。
下面我們來(lái)演示一下這個(gè)現(xiàn)象:
#include?<stdio.h> #include?<string.h>int?main() {char?name[10];???????????????????????/*?Such?as?"Beijing"?*/int?var1?=?1,?var2?=?2;/*?show?initial?values?*/printf("var1?=?%d;?var2?=?%d\n",?var1,?var2);/*?this?is?bad?..?please?don't?use?gets?*/puts("Where?do?you?live?");gets(name);/*?show?ending?values?*/printf("<%s>?is?length?%d\n",?name,?strlen(name));printf("var1?=?%d;?var2?=?%d\n",?var1,?var2);/*?done?*/puts("Ok");return?0; }在這段代碼里,接收數(shù)組的長(zhǎng)度為 10 ,所以當(dāng)輸入數(shù)據(jù)長(zhǎng)度小于 10 的話,程序運(yùn)行就沒(méi)問(wèn)題。
例如,輸入城市 Beijing ,長(zhǎng)度為 7 :
var1?=?1;?var2?=?2 Where?do?you?live? Beijing <Beijing>?is?length?7 var1?=?1;?var2?=?2 Ok威爾士小鎮(zhèn) Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 是世界上名字最長(zhǎng)的城市,這個(gè)字符串有 58 個(gè)字符,遠(yuǎn)遠(yuǎn)超出了 name 變量中可保留的 10 個(gè)字符。
如果輸入這個(gè)字符串,其結(jié)果是程序運(yùn)行內(nèi)存的其它位置,比如 var1和var2 ,都有可能被波及:
var1?=?1;?var2?=?2 Where?do?you?live? Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch <Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch>?is?length?58 var1?=?2036821625;?var2?=?2003266668 Ok Segmentation?fault?(core?dumped)在中止之前,程序使用長(zhǎng)字符串覆蓋內(nèi)存的其他部分。請(qǐng)注意,var1 和 var2 不再是它們的起始值 1 和 2 。
所以我們需要使用更安全的方法來(lái)讀取用戶數(shù)據(jù)。例如,getline 函數(shù)就是一個(gè)不錯(cuò)的選擇,它將分配足夠大的內(nèi)存來(lái)存儲(chǔ)用戶輸入,因此用戶不會(huì)因輸入太長(zhǎng)字符串而意外溢出。
4. 內(nèi)存重復(fù)釋放
良好的 C 編程規(guī)則之一是,如果分配了內(nèi)存,就一定要將其釋放。
我們可以使用 malloc 函數(shù)為數(shù)組和字符串申請(qǐng)內(nèi)存,系統(tǒng)將開(kāi)辟一塊內(nèi)存并返回一個(gè)指向該內(nèi)存起始地址的指針。內(nèi)存使用完畢后,我們一定要記得使用 free 函數(shù)釋放內(nèi)存,然后系統(tǒng)將該內(nèi)存標(biāo)記為未使用。
但是,這個(gè)過(guò)程中,你只能調(diào)用 free 函數(shù)一次。如果你第二次調(diào)用 free 函數(shù),將導(dǎo)致意外行為,而且可能會(huì)破壞你的程序。
下面我們舉個(gè)簡(jiǎn)單的例子:
#include?<stdio.h> #include?<stdlib.h>int?main() {int?*array;puts("malloc?an?array?...");array?=?malloc(sizeof(int)?*?5);if?(array)?{puts("malloc?succeeded");puts("Free?the?array...");free(array);}puts("Free?the?array...");free(array);puts("Ok"); }運(yùn)行此程序會(huì)導(dǎo)致第二次調(diào)用 free 函數(shù)時(shí)出現(xiàn) core dump 錯(cuò)誤:
malloc?an?array?... malloc?succeeded Free?the?array... Free?the?array... free():?double?free?detected?in?tcache?2 Aborted?(core?dumped)那么怎么避免多次調(diào)用 free 函數(shù)呢?一個(gè)最簡(jiǎn)單的方法就是將 malloc 和 free 語(yǔ)句放在一個(gè)函數(shù)里。
如果你將 malloc 放在一個(gè)函數(shù)里,而將 free 放在另一個(gè)函數(shù)里,那么,在使用的過(guò)程中,如果邏輯設(shè)計(jì)不恰當(dāng),都有可能出現(xiàn) free 被調(diào)用多次的情況。
5. 使用無(wú)效的文件指針
文件是操作系統(tǒng)里一種非常常見(jiàn)的數(shù)據(jù)存儲(chǔ)方式。例如,您可以將程序的配置信息存儲(chǔ)在名為 config.dat 文件里,程序運(yùn)行時(shí),就可以調(diào)用這個(gè)文件,讀取配置信息。
因此,從文件中讀取數(shù)據(jù)的能力對(duì)所有程序員都很重要。但是,如果你要讀取的文件不存在怎么辦?
在 C 語(yǔ)言中,要讀取文件一般是先使用 fopen 函數(shù)打開(kāi)文件,然后該函數(shù)返回指向文件的流指針。
如果您要讀取的文件不存在或您的程序無(wú)法讀取,則 fopen 函數(shù)將返回 NULL 。在這種情況下,我們?nèi)匀粚?duì)其進(jìn)行操作,會(huì)發(fā)生什么情況?我們一起來(lái)看下:
#include?<stdio.h>int?main() {FILE?*pfile;int?ch;puts("Open?the?FILE.TXT?file?...");pfile?=?fopen("FILE.TXT",?"r");/*?you?should?check?if?the?file?pointer?is?valid,?but?we?skipped?that?*/puts("Now?display?the?contents?of?FILE.TXT?...");while?((ch?=?fgetc(pfile))?!=?EOF)?{printf("<%c>",?ch);}fclose(pfile);/*?done?*/puts("Ok");return?0; }當(dāng)你運(yùn)行這個(gè)程序時(shí),如果 FILE.TXT 這個(gè)文件不存在,那么 pfile 將返回 NULL。在這種情況下我們還對(duì) pfile 進(jìn)行寫(xiě)操作的話,會(huì)立刻導(dǎo)致 core dump :
Open?the?FILE.TXT?file?... Now?display?the?contents?of?FILE.TXT?... Segmentation?fault?(core?dumped)所以,我們要始終檢查文件指針是否有效。例如,在調(diào)用 fopen 函數(shù)打開(kāi)文件后,使用 if (pfile != NULL) 以確保指針是可以使用的。
小結(jié)
再有經(jīng)驗(yàn)的程序員都有可能犯錯(cuò)誤,所以寫(xiě)代碼的時(shí)候我們要嚴(yán)謹(jǐn)再嚴(yán)謹(jǐn)。但是,如果你養(yǎng)成一些良好的習(xí)慣,并添加一些額外的代碼來(lái)檢查這五種類型的錯(cuò)誤,則可以避免嚴(yán)重的 C 編程錯(cuò)誤。
上面介紹的 5 種常見(jiàn)錯(cuò)誤,你都寫(xiě)過(guò)哪些 Bug 呢?留言跟大家交流哦,看看誰(shuí)是 Bug 王!
推薦閱讀:
專輯|Linux文章匯總
專輯|程序人生
專輯|C語(yǔ)言
我的知識(shí)小密圈
關(guān)注公眾號(hào),后臺(tái)回復(fù)「1024」獲取學(xué)習(xí)資料網(wǎng)盤(pán)鏈接。
歡迎點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā),在看,您的每一次鼓勵(lì),我都將銘記于心~
總結(jié)
以上是生活随笔為你收集整理的这5个bug我不信你没有写过的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用xsd文件验证xml
- 下一篇: android网易云桌面歌词,网易云音乐