
大家好,我是良許,
計算機專業的小伙伴,在學校期間一定學過 C 語言,它是眾多高級語言的鼻祖,深入學習這門語言會對計算機原理、作業系統、記憶體管理等等底層相關的知識會有更深入的了解,所以我在直播的時候,多次強調大家一定要好好學習這門語言,
但是,即使是最有經驗的程式員也會寫出各種各樣的 Bug,本文就盤點一下學習或使用 C 語言程序中,非常容易出現的 5 個 Bug,以及如何規避這些 Bug,
這篇文章主要面向初學者,老鳥可以忽略哈(其實不少老鳥依然還會犯這些低級錯誤哦)~
1. 變數未初始化
當程式啟動時,系統會給它自動分配一塊記憶體,程式可以用它來存盤資料,所以如果你在定義一個變數時,在未初始化的情況下,它的值有可能是任意的,
但這也不是絕對的,有些環境就會在程式啟動時自動將記憶體「清零」,因此每個變數默認值都是零,考慮到可移植性,最好要將變數進行初始化,這是一名合格軟體工程師應該養成的好習慣,
我們來看下下面這個使用幾個變數和兩個陣列的示例程式:
#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;
}
這段程式沒有對變數進行初始化,所以變數的值有可能是隨機的,不一定是零,在我的電腦上它的運行結果如下 :
These variables are not initialized:
i = 0
j = 0
k = 32766
This array is not initialized:
numbers[0] = 0
numbers[1] = 0
numbers[2] = 4199024
numbers[3] = 0
numbers[4] = 0
malloc an array ...
This malloc'ed array is not initialized:
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 0
array[4] = 0
Ok
從結果可以看出,i 和 j 的值剛好是 0,但 k 值為 32766, 在 numbers 陣列中,大多數元素也恰好是零,除了第三個(4199024),
在不同的作業系統上編譯這段相同的程式,運行的結果有可能又是不一樣的,所以千萬不要覺得你的結果就是正確唯一的,一定要考慮可移植性,
例如,這是在 FreeDOS 上運行的相同程式的結果:
These variables are not initialized:
i = 0
j = 1074
k = 3120
This array is not initialized:
numbers[0] = 3106
numbers[1] = 1224
numbers[2] = 784
numbers[3] = 2926
numbers[4] = 1224
malloc an array ...
This malloc'ed array is not initialized:
array[0] = 3136
array[1] = 3136
array[2] = 14499
array[3] = -5886
array[4] = 219
Ok
可以看出來,運行的結果跟上面幾乎是天差地別,所以,對變數進行初始化將為你省去很多不必要的麻煩,也便于將來的除錯,
2. 陣列越界
在計算機世界里,都是從 0 開始計數,但總有人有意無意忘記這點,比如一個陣列長度為 10 ,想要獲取最后一個元素的值,總有人用 array[10] ……
別問,問就是我寫過……
新手朋友犯這種低級錯誤特別多,我們來看下陣列越界會發生什么,
#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;
}
請注意,程式初始化了陣列 numbers 所有元素的值(0~4),但是越界讀取了第 0~9 元素的值,可以看出來,前五個值是正確的,但之后鬼都不知道這些值會是什么:
This array has five elements (0 to 4)
numbers[0] = 0
numbers[1] = 1
numbers[2] = 2
numbers[3] = 3
numbers[4] = 4
numbers[5] = 0
numbers[6] = 4198512
numbers[7] = 0
numbers[8] = 1326609712
numbers[9] = 32764
malloc an array ...
This malloc'ed array also has five elements (0 to 4)
array[0] = 0
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 4
array[5] = 0
array[6] = 133441
array[7] = 0
array[8] = 0
array[9] = 0
Ok
所以大家在寫代碼程序中,一定要知道陣列的邊界,像這種資料讀取的還好,如果一旦對這些記憶體進行寫操作,直接就 core dump !
3. 字串溢位
在 C 編程語言中,字串是一組 char 值,也可以將其視為陣列,因此,你也需要避免超出字串的范圍,如果超出,則稱為字串溢位,
為了測驗字串溢位,一種簡單方法是使用 gets 函式讀取資料,gets 函式非常危險,因為它不知道接收它的字串中可以存盤多少資料,只會天真地從用戶那里讀取資料,
如果用戶輸入字串比較短那很好,但如果用戶輸入的值超過接收字串的長度,則可能是災難性的,
下面我們來演示一下這個現象:
#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;
}
在這段代碼里,接收陣列的長度為 10 ,所以當輸入資料長度小于 10 的話,程式運行就沒問題,
例如,輸入城市 Beijing ,長度為 7 :
var1 = 1; var2 = 2
Where do you live?
Beijing
<Beijing> is length 7
var1 = 1; var2 = 2
Ok
威爾士小鎮 Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 是世界上名字最長的城市,這個字串有 58 個字符,遠遠超出了 name 變數中可保留的 10 個字符,
如果輸入這個字串,其結果是程式運行記憶體的其它位置,比如 var1和var2 ,都有可能被波及:
var1 = 1; var2 = 2
Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
var1 = 2036821625; var2 = 2003266668
Ok
Segmentation fault (core dumped)
在中止之前,程式使用長字串覆寫記憶體的其他部分,請注意,var1 和 var2 不再是它們的起始值 1 和 2 ,
所以我們需要使用更安全的方法來讀取用戶資料,例如,getline 函式就是一個不錯的選擇,它將分配足夠大的記憶體來存盤用戶輸入,因此用戶不會因輸入太長字串而意外溢位,
4. 記憶體重復釋放
良好的 C 編程規則之一是,如果分配了記憶體,就一定要將其釋放,
我們可以使用 malloc 函式為陣列和字串申請記憶體,系統將開辟一塊記憶體并回傳一個指向該記憶體起始地址的指標,記憶體使用完畢后,我們一定要記得使用 free 函式釋放記憶體,然后系統將該記憶體標記為未使用,
但是,這個程序中,你只能呼叫 free 函式一次,如果你第二次呼叫 free 函式,將導致意外行為,而且可能會破壞你的程式,
下面我們舉個簡單的例子:
#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");
}
運行此程式會導致第二次呼叫 free 函式時出現 core dump 錯誤:
malloc an array ...
malloc succeeded
Free the array...
Free the array...
free(): double free detected in tcache 2
Aborted (core dumped)
那么怎么避免多次呼叫 free 函式呢?一個最簡單的方法就是將 malloc 和 free 陳述句放在一個函式里,
如果你將 malloc 放在一個函式里,而將 free 放在另一個函式里,那么,在使用的程序中,如果邏輯設計不恰當,都有可能出現 free 被呼叫多次的情況,
5. 使用無效的檔案指標
檔案是作業系統里一種非常常見的資料存盤方式,例如,您可以將程式的配置資訊存盤在名為 config.dat 檔案里,程式運行時,就可以呼叫這個檔案,讀取配置資訊,
因此,從檔案中讀取資料的能力對所有程式員都很重要,但是,如果你要讀取的檔案不存在怎么辦?
在 C 語言中,要讀取檔案一般是先使用 fopen 函式打開檔案,然后該函式回傳指向檔案的流指標,
如果您要讀取的檔案不存在或您的程式無法讀取,則 fopen 函式將回傳 NULL ,在這種情況下,我們仍然對其進行操作,會發生什么情況?我們一起來看下:
#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;
}
當你運行這個程式時,如果 FILE.TXT 這個檔案不存在,那么 pfile 將回傳 NULL,在這種情況下我們還對 pfile 進行寫操作的話,會立刻導致 core dump :
Open the FILE.TXT file ...
Now display the contents of FILE.TXT ...
Segmentation fault (core dumped)
所以,我們要始終檢查檔案指標是否有效,例如,在呼叫 fopen 函式打開檔案后,使用 if (pfile != NULL) 以確保指標是可以使用的,
小結
再有經驗的程式員都有可能犯錯誤,所以寫代碼的時候我們要嚴謹再嚴謹,但是,如果你養成一些良好的習慣,并添加一些額外的代碼來檢查這五種型別的錯誤,則可以避免嚴重的 C 編程錯誤,
上面介紹的 5 種常見錯誤,你都寫過哪些 Bug 呢?留言跟大家交流哦,看看誰是 Bug 王!
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/340294.html
標籤:其他
上一篇:人人都寫過的5個Bug!
