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