血淚教訓(xùn)!這 17 個(gè) C 語言段錯(cuò)誤陷阱害慘了無數(shù)程序員
哈嘍大家好!我是小康。
今天咱們來聊聊 C 語言里最讓新手崩潰的東西——段錯(cuò)誤(Segmentation Fault)!
你是不是也有過這樣的經(jīng)歷:代碼寫得好好的,一運(yùn)行就彈出"段錯(cuò)誤",然后程序直接閃退?那種心情就像是精心準(zhǔn)備的飯菜,結(jié)果一上桌就翻了!
別慌!今天我就帶你揪出這些隱藏的"坑",看完之后保證你恍然大悟:"原來如此!"

坑1:空指針的致命一擊
這個(gè)絕對(duì)是新手殺手第一名!看這個(gè)代碼:
#include <stdio.h>
int main() {
    int *ptr = NULL;
    
    printf("準(zhǔn)備訪問空指針...\n");
    *ptr = 100;  // ?? 炸了!段錯(cuò)誤!
    printf("這句話永遠(yuǎn)不會(huì)執(zhí)行\(zhòng)n");
    
    return 0;
}運(yùn)行結(jié)果:
準(zhǔn)備訪問空指針...
Segmentation fault (core dumped)為什么會(huì)炸? 就像你想往一個(gè)不存在的地址寄快遞一樣,NULL指針指向的是"虛無",你往虛無里塞東西,系統(tǒng)當(dāng)然要炸毛??!
正確姿勢(shì):
#include <stdio.h>
int main() {
    int *ptr = NULL;
    
    if (ptr != NULL) {  // 先檢查一下
        *ptr = 100;
    } else {
        printf("指針是空的,不能用!\n");
    }
    
    return 0;
}坑2:數(shù)組越界——跑出安全區(qū)
這個(gè)坑也是相當(dāng)經(jīng)典!就像在游戲里跑出了地圖邊界:
#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("正常訪問:%d\n", arr[2]);  // 輸出:3
    printf("危險(xiǎn)操作:%d\n", arr[100]); // ?? 可能段錯(cuò)誤!
    
    // 更危險(xiǎn)的寫操作
    arr[10000] = 999;  // ?? 幾乎必定段錯(cuò)誤!
    
    return 0;
}為什么會(huì)出事? 數(shù)組就像一排房子,你有5間房(索引0-4),結(jié)果你跑到第100間房去放東西,那不是別人家的地盤嗎?系統(tǒng)肯定不答應(yīng)!
安全做法:
#include <stdio.h>
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int index = 10;
    
    if (index >= 0 && index < 5) {
        printf("安全訪問:%d\n", arr[index]);
    } else {
        printf("索引%d超出范圍了!\n", index);
    }
    
    return 0;
}坑3:野指針——指向未知的危險(xiǎn)
野指針就像一個(gè)喝醉酒的人,不知道會(huì)指向哪里:
#include <stdio.h>
int main() {
    int *ptr;  // 沒有初始化,是個(gè)野指針!
    
    printf("野指針的值:%p\n", ptr);  // 打印一個(gè)隨機(jī)地址
    *ptr = 42;  // ?? 向隨機(jī)地址寫數(shù)據(jù),危險(xiǎn)!
    
    return 0;
}
// 輸出:
/*
野指針的值:(nil)
Segmentation fault (core dumped)
*/為什么危險(xiǎn)? 野指針就像一個(gè)沒有目標(biāo)的導(dǎo)彈,你不知道它會(huì)炸到哪里!可能是系統(tǒng)重要的內(nèi)存區(qū)域,那就完蛋了!
正確初始化:
#include <stdio.h>
int main() {
    int value = 10;
    int *ptr = &value;  // 讓指針指向一個(gè)確定的地址
    
    printf("安全操作:%d\n", *ptr);  // 輸出:10
    *ptr = 42;  // 安全的寫操作
    printf("修改后:%d\n", *ptr);    // 輸出:42
    
    return 0;
}坑4:釋放后繼續(xù)使用——鞭尸行為
這個(gè)錯(cuò)誤就像你把房子賣了,還想回去住一樣:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("釋放前:%d\n", *ptr);  // 輸出:100
    
    free(ptr);  // 釋放內(nèi)存
    
    // 致命錯(cuò)誤:繼續(xù)使用已釋放的內(nèi)存
    printf("釋放后:%d\n", *ptr);  // ?? 未定義行為,可能段錯(cuò)誤!
    *ptr = 200;  // ?? 更加危險(xiǎn)!
    
    return 0;
}正確做法:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("使用中:%d\n", *ptr);  // 輸出:100
    
    free(ptr);
    ptr = NULL;  // 釋放后立即置空,防止誤用
    
    if (ptr != NULL) {
        printf("繼續(xù)使用:%d\n", *ptr);
    } else {
        printf("指針已釋放,不能再用了!\n");
    }
    
    return 0;
}坑5:棧溢出——遞歸的無底洞
遞歸用不好就像掉進(jìn)了無底洞:
#include <stdio.h>
int badRecursion(int n) {
    printf("遞歸層數(shù):%d\n", n);
    return badRecursion(n + 1);  // ?? 永遠(yuǎn)不會(huì)停止!
}
int main() {
    badRecursion(1);  // ?? 棧溢出段錯(cuò)誤!
    return 0;
}為什么會(huì)炸? 就像一個(gè)人不停地往地下室走,總有一天會(huì)撞到地板!棧空間有限,遞歸太深就會(huì)溢出。
安全的遞歸:
#include <stdio.h>
int safeRecursion(int n) {
    if (n <= 0) {  // 遞歸出口
        return1;
    }
    printf("遞歸層數(shù):%d\n", n);
    return n * safeRecursion(n - 1);
}
int main() {
    int result = safeRecursion(5);
    printf("結(jié)果:%d\n", result);  // 輸出:120
    return 0;
}坑6:字符串操作越界
字符串操作不小心就會(huì)越界:
#include <stdio.h>
#include <string.h>
int main() {
    char str[5] = "Hi";  // 只能裝4個(gè)字符+'\0'
    
    printf("原字符串:%s\n", str);  // 輸出:Hi
    
    // 危險(xiǎn)操作:拷貝太長(zhǎng)的字符串
    strcpy(str, "Hello World!");  // ?? 緩沖區(qū)溢出!
    
    printf("拷貝后:%s\n", str);  // 可能段錯(cuò)誤或垃圾數(shù)據(jù)
    
    return 0;
}
/*
輸出結(jié)果:
原字符串:Hi
拷貝后:Hello World!
*** stack smashing detected ***: terminated
Aborted (core dumped)
*/安全做法:
#include <stdio.h>
#include <string.h>
int main() {
    char str[20] = "Hi";  // 給足夠的空間
    
    printf("原字符串:%s\n", str);  // 輸出:Hi
    
    // 安全拷貝
    if (strlen("Hello World!") < sizeof(str)) {
        strcpy(str, "Hello World!");
        printf("拷貝后:%s\n", str);  // 輸出:Hello World!
    } else {
        printf("字符串太長(zhǎng),裝不下!\n");
    }
    
    return 0;
}坑7:返回局部變量地址——過期的房產(chǎn)證
這個(gè)錯(cuò)誤就像把一個(gè)即將拆遷的房子地址給別人:
#include <stdio.h>
int* getDangerousPointer() {
    int localVar = 42;
    return &localVar;  // ?? 返回局部變量地址!
}
int main() {
    int *ptr = getDangerousPointer();
    printf("危險(xiǎn)的值:%d\n", *ptr);  // ?? 可能段錯(cuò)誤或垃圾值!
    
    return 0;
}
/*
輸出:
Segmentation fault (core dumped)
*/為什么危險(xiǎn)? 局部變量存在棧上,函數(shù)結(jié)束后就被銷毀了。你返回它的地址,就像給別人一個(gè)已經(jīng)被拆掉的房子的鑰匙!
正確做法:
#include <stdio.h>
#include <stdlib.h>
int* getSafePointer() {
    int *ptr = (int*)malloc(sizeof(int));  // 在堆上分配
    *ptr = 42;
    return ptr;  // 安全返回
}
int main() {
    int *ptr = getSafePointer();
    printf("安全的值:%d\n", *ptr);  // 輸出:42
    
    free(ptr);  // 記得釋放
    ptr = NULL;
    
    return 0;
}坑8:多次釋放同一塊內(nèi)存——重復(fù)拆房子
這就像你把同一棟房子拆了兩次:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("使用內(nèi)存:%d\n", *ptr);  // 輸出:100
    
    free(ptr);     // 第一次釋放,正常
    free(ptr);     // ?? 第二次釋放,段錯(cuò)誤!
    
    return0;
}
/*
輸出:
使用內(nèi)存:100
free(): double free detected in tcache 2
Aborted (core dumped)
*/安全做法:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("使用內(nèi)存:%d\n", *ptr);  // 輸出:100
    
    if (ptr != NULL) {
        free(ptr);
        ptr = NULL;  // 釋放后立即置空
    }
    
    if (ptr != NULL) {  // 再次檢查
        free(ptr);
    } else {
        printf("指針已經(jīng)是空的,不需要釋放\n");
    }
    
    return 0;
}坑9:格式化字符串漏洞
printf系列函數(shù)用不好也會(huì)出事:
#include <stdio.h>
int main() {
    char dangerous[] = "Name: %s, Age: %s, City: %s, Job: %s";
    
     // 危險(xiǎn):直接把用戶輸入當(dāng)格式字符串
    printf(dangerous);  // 4個(gè)%s都會(huì)從棧上取隨機(jī)指針 ?? 可能段錯(cuò)誤!
    return 0;
}為什么危險(xiǎn)? printf會(huì)按照格式字符串去棧上找參數(shù),但你沒提供參數(shù),它就會(huì)讀取棧上的垃圾數(shù)據(jù),甚至越界訪問!
正確做法:
#include <stdio.h>
int main() {
    char userInput[] = "Name: %s, Age: %s, City: %s, Job: %s";
    
    // 安全:用%s格式化輸出字符串
    printf("%s\n", userInput);
    
    return 0;
}坑10:忘記檢查malloc返回值
malloc也有失敗的時(shí)候,不檢查就是在玩火:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> 
int main() {
    // 申請(qǐng)超大內(nèi)存,可能失敗
    int *ptr = (int*)malloc(SIZE_MAX);
    
    *ptr = 100;  // ?? 如果malloc失敗,ptr是NULL,段錯(cuò)誤!
    printf("值:%d\n", *ptr);
    
    free(ptr);
    return 0;
}
//輸出:Segmentation fault (core dumped)安全做法:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    
    if (ptr == NULL) {  // 檢查分配是否成功
        printf("內(nèi)存分配失敗!\n");
        return1;
    }
    
    *ptr = 100;
    printf("值:%d\n", *ptr);  // 輸出:100
    
    free(ptr);
    return 0;
}坑11:二維數(shù)組指針混亂
二維數(shù)組和指針一起用,新手最容易搞混:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int **matrix;
    
    // 錯(cuò)誤:只分配了指針數(shù)組,沒分配實(shí)際存儲(chǔ)空間
    matrix = (int**)malloc(3 * sizeof(int*));
    
    matrix[0][0] = 10;  // ?? 段錯(cuò)誤!matrix[0]是垃圾值
    
    return 0;
}
// 輸出:Segmentation fault (core dumped)正確的二維數(shù)組分配:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int **matrix;
    int rows = 3, cols = 4;
    
    // 先分配指針數(shù)組
    matrix = (int**)malloc(rows * sizeof(int*));
    
    // 再為每一行分配空間
    for(int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
    }
    
    // 現(xiàn)在可以安全使用了
    matrix[0][0] = 10;
    printf("安全賦值:%d\n", matrix[0][0]);  // 輸出:10
    
    // 釋放內(nèi)存
    for(int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    
    return 0;
}坑12:結(jié)構(gòu)體內(nèi)指針未初始化
結(jié)構(gòu)體里的指針成員經(jīng)常被忘記初始化:
#include <stdio.h>
#include <stdlib.h>
struct Student {
    char *name;
    int age;
};
int main() {
    struct Student stu;
    stu.age = 18;
    
    // 危險(xiǎn):name指針沒有初始化就使用
    strcpy(stu.name, "小明");  // ?? 可能段錯(cuò)誤!
    
    return 0;
}正確做法:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
    char *name;
    int age;
};
int main() {
    struct Student stu;
    stu.age = 18;
    
    // 先為name分配空間
    stu.name = (char*)malloc(20 * sizeof(char));
    strcpy(stu.name, "小明");
    
    printf("學(xué)生姓名:%s,年齡:%d\n", stu.name, stu.age);
    
    free(stu.name);  // 記得釋放
    return 0;
}坑13:函數(shù)參數(shù)傳遞陷阱
指針作為參數(shù)傳遞時(shí)的常見錯(cuò)誤:
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int *ptr) {
    ptr = (int*)malloc(sizeof(int));  // ?? 這樣寫沒用!
    *ptr = 100;
}
int main() {
    int *myPtr = NULL;
    allocateMemory(myPtr);
    
    printf("值:%d\n", *myPtr);  // ?? 段錯(cuò)誤!myPtr還是NULL
    
    return 0;
}
// 輸出:Segmentation fault (core dumped)正確的指針參數(shù)傳遞:
#include <stdio.h>
#include <stdlib.h>
void allocateMemory(int **ptr) {  // 傳遞指針的指針
    *ptr = (int*)malloc(sizeof(int));
    **ptr = 100;
}
int main() {
    int *myPtr = NULL;
    allocateMemory(&myPtr);  // 傳遞指針的地址
    
    printf("值:%d\n", *myPtr);  // 輸出:100
    
    free(myPtr);
    return 0;
}坑14:字符串字面量修改
這個(gè)坑特別隱蔽,很多人不知道:
#include <stdio.h>
int main() {
    char *str = "Hello";  // 字符串字面量存在只讀區(qū)域
    
    printf("原字符串:%s\n", str);  // 輸出:Hello
    
    str[0] = 'h';  // ?? 試圖修改只讀內(nèi)存,段錯(cuò)誤!
    
    return 0;
}
// 輸出:原字符串:Hello
// Segmentation fault (core dumped)正確做法:
#include <stdio.h>
int main() {
    char str[] = "Hello";  // 數(shù)組,可以修改
    // 或者 char str[10] = "Hello";
    
    printf("原字符串:%s\n", str);  // 輸出:Hello
    
    str[0] = 'h';  // 安全修改
    printf("修改后:%s\n", str);    // 輸出:hello
    
    return 0;
}坑15:聯(lián)合體內(nèi)存覆蓋陷阱
union使用不當(dāng)也會(huì)造成意外:
#include <stdio.h>
union Data {
    int intVal;
    float floatVal;
    char *strVal;
};
int main() {
    union Data data;
    
    data.strVal = "Hello";
    printf("字符串:%s\n", data.strVal);  // 輸出:Hello
    
    data.intVal = 100;  // 覆蓋了strVal的值
    printf("整數(shù):%d\n", data.intVal);    // 輸出:100
    
    // 危險(xiǎn):strVal現(xiàn)在是垃圾值
    printf("字符串:%s\n", data.strVal);  // ?? 可能段錯(cuò)誤!
    
    return 0;
}
/* 輸出:
字符串:Hello
整數(shù):100
Segmentation fault (core dumped)
*/安全使用聯(lián)合體:
#include <stdio.h>
enum DataType {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
};
struct SafeData {
    enum DataType type;
    union {
        int intVal;
        float floatVal;
        char *strVal;
    } value;
};
int main() {
    struct SafeData data;
    
    // 設(shè)置字符串
    data.type = TYPE_STRING;
    data.value.strVal = "Hello";
    
    if (data.type == TYPE_STRING) {
        printf("字符串:%s\n", data.value.strVal);
    }
    
    // 改為整數(shù)類型
    data.type = TYPE_INT;
    data.value.intVal = 100;
    
    if (data.type == TYPE_INT) {
        printf("整數(shù):%d\n", data.value.intVal);
    }
    
    return 0;
}坑16:函數(shù)指針未檢查
函數(shù)指針為NULL時(shí)調(diào)用會(huì)段錯(cuò)誤:
#include <stdio.h>
void sayHello() {
    printf("Hello!\n");
}
int main() {
    void (*funcPtr)() = NULL;
    
    // 某些情況下可能給funcPtr賦值,某些情況下可能忘記
    if (rand() % 2 == 0) {
        funcPtr = sayHello;
    }
    
    funcPtr();  // ?? 如果funcPtr還是NULL,段錯(cuò)誤!
    
    return 0;
}安全調(diào)用函數(shù)指針:
#include <stdio.h>
void sayHello() {
    printf("Hello!\n");
}
void sayGoodbye() {
    printf("Goodbye!\n");
}
int main() {
    void (*funcPtr)() = NULL;
    
    // 根據(jù)條件設(shè)置函數(shù)指針
    if (rand() % 2 == 0) {
        funcPtr = sayHello;
    } else {
        funcPtr = sayGoodbye;
    }
    
    // 安全調(diào)用
    if (funcPtr != NULL) {
        funcPtr();
    } else {
        printf("函數(shù)指針為空,無法調(diào)用!\n");
    }
    
    return 0;
}坑17:競(jìng)態(tài)條件導(dǎo)致的段錯(cuò)誤
多線程環(huán)境下的段錯(cuò)誤更難調(diào)試:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int *global_ptr = NULL;
void* thread_func(void* arg) {
    if (global_ptr != NULL) {
        sleep(1);
        // ?? 另一個(gè)線程可能在這里釋放了global_ptr
        *global_ptr = 100;  // 可能段錯(cuò)誤!
    }
    return NULL;
}
int main() {
    pthread_t thread;
    
    global_ptr = (int*)malloc(sizeof(int));
    
    pthread_create(&thread, NULL, thread_func, NULL);
    
    // 主線程釋放內(nèi)存
    free(global_ptr);
    global_ptr = NULL;
    
    pthread_join(thread, NULL);
    return 0;
}正確做法:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
int *global_ptr = NULL;
void* thread_func(void* arg) {
    if (global_ptr != NULL) {
        sleep(1);  // 模擬一些操作
        *global_ptr = 100;
        printf("設(shè)置值成功:%d\n", *global_ptr);
    }
    return NULL;
}
int main() {
    pthread_t thread;
    
    global_ptr = (int*)malloc(sizeof(int));
    
    pthread_create(&thread, NULL, thread_func, NULL);
    
    // 等子線程完成再釋放!這才是關(guān)鍵
    pthread_join(thread, NULL);
    
    // 現(xiàn)在可以安全釋放了
    free(global_ptr);
    global_ptr = NULL;
    
    return 0;
}避坑終極秘籍
看完這 17 個(gè)經(jīng)典陷阱,相信你已經(jīng)對(duì)段錯(cuò)誤有了全新的認(rèn)識(shí)!讓我總結(jié)幾個(gè)終極避坑秘籍:
內(nèi)存管理黃金法則:
- malloc必須配free - 有借有還,再借不難
 - free后立即置NULL - 防止野指針復(fù)活
 - 使用前必須檢查 - NULL指針是大敵
 - 邊界時(shí)刻要注意 - 數(shù)組越界是噩夢(mèng)
 
指針使用終極口訣:
- 初始化 - 聲明時(shí)就給個(gè)明確的值
 - 驗(yàn)證 - 使用前檢查是否為NULL
 - 保護(hù) - 操作時(shí)注意邊界和權(quán)限
 - 清理 - 用完立即釋放并置空
 
多級(jí)指針避坑技巧:
- 二維數(shù)組 - 先分配行指針,再分配每行數(shù)據(jù)
 - 函數(shù)傳參 - 想改指針本身,就傳指針的地址
 - 結(jié)構(gòu)體指針 - 內(nèi)部的指針成員別忘了初始化
 
字符串操作安全守則:
- 只讀區(qū)別碰 - 字符串字面量不能修改
 - 空間要充足 - strcpy前確保目標(biāo)夠大
 - 邊界要檢查 - 防止緩沖區(qū)溢出
 
高級(jí)避坑技巧:
- 聯(lián)合體慎用 - 記住類型,避免數(shù)據(jù)覆蓋
 - 函數(shù)指針檢查 - NULL函數(shù)指針不能調(diào)用
 - 多線程加鎖 - 共享資源要保護(hù)
 
記住這些,再配合調(diào)試工具(gdb、valgrind、AddressSanitizer),段錯(cuò)誤再也不是你的攔路虎!















 
 
 




 
 
 
 