段錯誤相信是每一個C語言初學者都會遇到的一個問題,很多初學者看到這個錯誤就開始抓狂。
但是沒寫過段錯誤的程序員不是個合格的程序員!
一口君寫了這么多年代碼,有時候還是會出現(xiàn)段錯誤。
下面給大家整理了一些C 語言典型的段錯誤(Segmentation Fault)實例及代碼示例,按常見場景分類說明:
1. ?引用空指針?
#include?<stdio.h>
int?main()?{
? ??int?*p =?NULL;
?
? ??printf("%dn", *p);?// 解引用空指針
? ??return?0;
}
?原因?:p 未指向有效內(nèi)存地址。
2. ?訪問受保護的內(nèi)存地址?
int?*p = (int*)1; ?// 強制將指針指向地址 0x1
*p =?100; ? ? ? ? ?// 訪問系統(tǒng)保護的內(nèi)存區(qū)域
?原因?:嘗試操作內(nèi)核或系統(tǒng)保留的內(nèi)存區(qū)域。
3. ?修改字符串常量?
char?*str =?"hello"; ?// 字符串常量存儲在只讀區(qū)
str[0] =?'H'; ? ? ? ??// 嘗試修改常量區(qū)數(shù)據(jù)
?原因?:字符串字面量存儲在只讀內(nèi)存段,不可被修改6。
4. ?棧溢出?
void?infinite_loop()?{
? ? infinite_loop(); ?// 無限遞歸導致棧空間耗盡
}
int?main()?
{?
?infinite_loop();?
}
?原因?:無限遞歸導致棧內(nèi)存溢出6。
5. ?數(shù)組越界訪問?
int?arr[5];
arr[5] =?10; ?// 合法索引為 0~4,越界訪問無效內(nèi)存
?原因?:訪問超出數(shù)組定義大小的內(nèi)存區(qū)域。
#include?<stdio.h>
?
int?main()?{?
? ??int?arr[5] = {0,?1,?2,?3,?4};
?
? ??printf("%dn", arr[10]);?// 訪問不存在的元素
? ??return?0;
}
執(zhí)行結(jié)果:未定義行為,可能會導致程序崩潰或打印出垃圾值。
數(shù)組越界是一些新手最容易出錯的地方,經(jīng)常因為數(shù)組下標控制不好,導致訪問越界,而這種情況可能99%幾率不是立刻報段錯誤,也可能程序運行幾年都不報錯, 但是它一旦報了錯,就會特別隱蔽,非常難查。
剛工作的時候在zte,曾經(jīng)有2位大佬追一個德國運營商現(xiàn)場報的bug,花了一個月時間,最后發(fā)現(xiàn)是數(shù)組越界導致。
6. ?使用未初始化的指針?
int?*p; ? ? ? ?// 未初始化指針
*p =?42; ? ? ??// 野指針指向無效地址
?原因?:指針未指向有效內(nèi)存空間。
7. ?訪問已釋放的內(nèi)存?
int?*p =?malloc(sizeof(int));
free(p);
*p =?10; ? ? ??// 內(nèi)存釋放后繼續(xù)使用
?原因?:操作已被釋放的動態(tài)內(nèi)存區(qū)域。
8. ?緩沖區(qū)溢出?
char?buffer[5];
strcpy(buffer,?"HelloWorld"); ?// 超出 buffer 容量
?原因?:字符串操作超過目標緩沖區(qū)大小。
9. ?雙重釋放內(nèi)存?
int?*p =?malloc(sizeof(int));
free(p);
free(p); ?// 重復釋放同一塊內(nèi)存
?原因?:多次釋放同一內(nèi)存導致堆管理器異常。
10. ?強制類型轉(zhuǎn)換錯誤?
int?num =?42;
char?*p = (char*)num; ?// 將整數(shù)值強制轉(zhuǎn)換為地址
*p =?'A'; ? ? ? ? ? ? ?// 訪問非法地址
?原因?:將非指針類型強制轉(zhuǎn)換為指針并解引用。
11.格式化字符串與參數(shù)類型不匹配示例
int?data =?0;
sprintf(buf,"%s",data);
12、忘記字符串結(jié)尾的空字符示例:
#include?<stdio.h>
?
int?main()?{
?
? ??char?str[5] = {'H',?'e',?'l',?'l',?'o'};?// 缺少 ''
?
? ??printf("%sn", str);
? ??return?0;
}
執(zhí)行結(jié)果:未定義行為,可能會打印出亂碼直到遇到一個’’。
13、緩沖區(qū)溢出示例:
#include?<stdio.h>
#include?<string.h>
int?main()?{
? ??char?dest[5];
? ??
? ??strcpy(dest,?"Hello, World!");?// 目標緩沖區(qū)太小
? ??printf("%sn", dest);
? ??return?0;
}
執(zhí)行結(jié)果:未定義行為,可能會崩潰或覆蓋內(nèi)存。
14、未檢查類型大小示例:
#include?<stdio.h>
?
int?main()?{
? ??char?*p = (char?*)malloc(10?*?sizeof(int));
? ??int?*q = (int?*)p;?// 錯誤的假設(shè)char和int大小相同
?
? ??for?(int?i =?0; i <?10; ++i) {
? ? ? ? q[i] = i;?// 可能導致內(nèi)存越界
? ? }
?
? ??free(p);
? ??return?0;
}
執(zhí)行結(jié)果:未定義行為,可能會導致內(nèi)存越界。
15、變量未正確初始化示例:
#include?<stdio.h>
?
int?main()?{
? ??int?num =?123;
?
? ??printf("%sn", num);?// 錯誤的格式化字符串,應為%d
? ??return?0;
}
執(zhí)行結(jié)果:未定義行為,可能會打印出任意值。
16、忽視錯誤返回值示例:
#include?<stdio.h>
#include?<stdlib.h>
int?main()?{
?
? ? FILE *file = fopen("nonexistent.txt",?"r");
? ??if?(!file) {
? ? ? ??// 忽視錯誤,沒有處理
? ? }
?
? ??// 使用file...
? ? fclose(file);
? ??return?0;
}
執(zhí)行結(jié)果:如果文件不存在,程序會嘗試使用未初始化的指針,可能導致崩潰。
總結(jié)
段錯誤本質(zhì)是訪問了非法內(nèi)存地址,可通過以下方式避免:
- 初始化指針并檢查有效性;避免越界操作數(shù)組或緩沖區(qū);謹慎處理動態(tài)內(nèi)存的分配與釋放;區(qū)分常量區(qū)與變量區(qū)的數(shù)據(jù)修改權(quán)限對一些庫函數(shù)返回值一定要判斷