成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

血淚教訓!這 17 個 C 語言段錯誤陷阱害慘了無數程序員

開發
今天咱們來聊聊 C 語言里最讓新手崩潰的東西——段錯誤(Segmentation Fault)!

哈嘍大家好!我是小康。

今天咱們來聊聊 C 語言里最讓新手崩潰的東西——段錯誤(Segmentation Fault)!

你是不是也有過這樣的經歷:代碼寫得好好的,一運行就彈出"段錯誤",然后程序直接閃退?那種心情就像是精心準備的飯菜,結果一上桌就翻了!

別慌!今天我就帶你揪出這些隱藏的"坑",看完之后保證你恍然大悟:"原來如此!"

坑1:空指針的致命一擊

這個絕對是新手殺手第一名!看這個代碼:

#include <stdio.h>

int main() {
    int *ptr = NULL;
    
    printf("準備訪問空指針...\n");
    *ptr = 100;  // ?? 炸了!段錯誤!
    printf("這句話永遠不會執行\n");
    
    return 0;
}

運行結果:

準備訪問空指針...
Segmentation fault (core dumped)

為什么會炸? 就像你想往一個不存在的地址寄快遞一樣,NULL指針指向的是"虛無",你往虛無里塞東西,系統當然要炸毛?。?/p>

正確姿勢:

#include <stdio.h>

int main() {
    int *ptr = NULL;
    
    if (ptr != NULL) {  // 先檢查一下
        *ptr = 100;
    } else {
        printf("指針是空的,不能用!\n");
    }
    
    return 0;
}

坑2:數組越界——跑出安全區

這個坑也是相當經典!就像在游戲里跑出了地圖邊界:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("正常訪問:%d\n", arr[2]);  // 輸出:3
    printf("危險操作:%d\n", arr[100]); // ?? 可能段錯誤!
    
    // 更危險的寫操作
    arr[10000] = 999;  // ?? 幾乎必定段錯誤!
    
    return 0;
}

為什么會出事? 數組就像一排房子,你有5間房(索引0-4),結果你跑到第100間房去放東西,那不是別人家的地盤嗎?系統肯定不答應!

安全做法:

#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:野指針——指向未知的危險

野指針就像一個喝醉酒的人,不知道會指向哪里:

#include <stdio.h>

int main() {
    int *ptr;  // 沒有初始化,是個野指針!
    
    printf("野指針的值:%p\n", ptr);  // 打印一個隨機地址
    *ptr = 42;  // ?? 向隨機地址寫數據,危險!
    
    return 0;
}

// 輸出:
/*
野指針的值:(nil)
Segmentation fault (core dumped)
*/

為什么危險? 野指針就像一個沒有目標的導彈,你不知道它會炸到哪里!可能是系統重要的內存區域,那就完蛋了!

正確初始化:

#include <stdio.h>

int main() {
    int value = 10;
    int *ptr = &value;  // 讓指針指向一個確定的地址
    
    printf("安全操作:%d\n", *ptr);  // 輸出:10
    *ptr = 42;  // 安全的寫操作
    printf("修改后:%d\n", *ptr);    // 輸出:42
    
    return 0;
}

坑4:釋放后繼續使用——鞭尸行為

這個錯誤就像你把房子賣了,還想回去住一樣:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("釋放前:%d\n", *ptr);  // 輸出:100
    
    free(ptr);  // 釋放內存
    
    // 致命錯誤:繼續使用已釋放的內存
    printf("釋放后:%d\n", *ptr);  // ?? 未定義行為,可能段錯誤!
    *ptr = 200;  // ?? 更加危險!
    
    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("繼續使用:%d\n", *ptr);
    } else {
        printf("指針已釋放,不能再用了!\n");
    }
    
    return 0;
}

坑5:棧溢出——遞歸的無底洞

遞歸用不好就像掉進了無底洞:

#include <stdio.h>

int badRecursion(int n) {
    printf("遞歸層數:%d\n", n);
    return badRecursion(n + 1);  // ?? 永遠不會停止!
}

int main() {
    badRecursion(1);  // ?? 棧溢出段錯誤!
    return 0;
}

為什么會炸? 就像一個人不停地往地下室走,總有一天會撞到地板!??臻g有限,遞歸太深就會溢出。

安全的遞歸:

#include <stdio.h>

int safeRecursion(int n) {
    if (n <= 0) {  // 遞歸出口
        return1;
    }
    printf("遞歸層數:%d\n", n);
    return n * safeRecursion(n - 1);
}

int main() {
    int result = safeRecursion(5);
    printf("結果:%d\n", result);  // 輸出:120
    return 0;
}

坑6:字符串操作越界

字符串操作不小心就會越界:

#include <stdio.h>
#include <string.h>

int main() {
    char str[5] = "Hi";  // 只能裝4個字符+'\0'
    
    printf("原字符串:%s\n", str);  // 輸出:Hi
    
    // 危險操作:拷貝太長的字符串
    strcpy(str, "Hello World!");  // ?? 緩沖區溢出!
    
    printf("拷貝后:%s\n", str);  // 可能段錯誤或垃圾數據
    
    return 0;
}
/*
輸出結果:

原字符串: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("字符串太長,裝不下!\n");
    }
    
    return 0;
}

坑7:返回局部變量地址——過期的房產證

這個錯誤就像把一個即將拆遷的房子地址給別人:

#include <stdio.h>

int* getDangerousPointer() {
    int localVar = 42;
    return &localVar;  // ?? 返回局部變量地址!
}

int main() {
    int *ptr = getDangerousPointer();
    printf("危險的值:%d\n", *ptr);  // ?? 可能段錯誤或垃圾值!
    
    return 0;
}
/*
輸出:
Segmentation fault (core dumped)
*/

為什么危險? 局部變量存在棧上,函數結束后就被銷毀了。你返回它的地址,就像給別人一個已經被拆掉的房子的鑰匙!

正確做法:

#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:多次釋放同一塊內存——重復拆房子

這就像你把同一棟房子拆了兩次:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    
    printf("使用內存:%d\n", *ptr);  // 輸出:100
    
    free(ptr);     // 第一次釋放,正常
    free(ptr);     // ?? 第二次釋放,段錯誤!
    
    return0;
}
/*
輸出:
使用內存: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("使用內存:%d\n", *ptr);  // 輸出:100
    
    if (ptr != NULL) {
        free(ptr);
        ptr = NULL;  // 釋放后立即置空
    }
    
    if (ptr != NULL) {  // 再次檢查
        free(ptr);
    } else {
        printf("指針已經是空的,不需要釋放\n");
    }
    
    return 0;
}

坑9:格式化字符串漏洞

printf系列函數用不好也會出事:

#include <stdio.h>

int main() {
    char dangerous[] = "Name: %s, Age: %s, City: %s, Job: %s";
    
     // 危險:直接把用戶輸入當格式字符串
    printf(dangerous);  // 4個%s都會從棧上取隨機指針 ?? 可能段錯誤!
    return 0;
}

為什么危險? printf會按照格式字符串去棧上找參數,但你沒提供參數,它就會讀取棧上的垃圾數據,甚至越界訪問!

正確做法:

#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也有失敗的時候,不檢查就是在玩火:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> 

int main() {
    // 申請超大內存,可能失敗
    int *ptr = (int*)malloc(SIZE_MAX);
    
    *ptr = 100;  // ?? 如果malloc失敗,ptr是NULL,段錯誤!
    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");
        return1;
    }
    
    *ptr = 100;
    printf("值:%d\n", *ptr);  // 輸出:100
    
    free(ptr);
    return 0;
}

坑11:二維數組指針混亂

二維數組和指針一起用,新手最容易搞混:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int **matrix;
    
    // 錯誤:只分配了指針數組,沒分配實際存儲空間
    matrix = (int**)malloc(3 * sizeof(int*));
    
    matrix[0][0] = 10;  // ?? 段錯誤!matrix[0]是垃圾值
    
    return 0;
}
// 輸出:Segmentation fault (core dumped)

正確的二維數組分配:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int **matrix;
    int rows = 3, cols = 4;
    
    // 先分配指針數組
    matrix = (int**)malloc(rows * sizeof(int*));
    
    // 再為每一行分配空間
    for(int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
    }
    
    // 現在可以安全使用了
    matrix[0][0] = 10;
    printf("安全賦值:%d\n", matrix[0][0]);  // 輸出:10
    
    // 釋放內存
    for(int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    
    return 0;
}

坑12:結構體內指針未初始化

結構體里的指針成員經常被忘記初始化:

#include <stdio.h>
#include <stdlib.h>

struct Student {
    char *name;
    int age;
};

int main() {
    struct Student stu;
    stu.age = 18;
    
    // 危險:name指針沒有初始化就使用
    strcpy(stu.name, "小明");  // ?? 可能段錯誤!
    
    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("學生姓名:%s,年齡:%d\n", stu.name, stu.age);
    
    free(stu.name);  // 記得釋放
    return 0;
}

坑13:函數參數傳遞陷阱

指針作為參數傳遞時的常見錯誤:

#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);  // ?? 段錯誤!myPtr還是NULL
    
    return 0;
}
// 輸出:Segmentation fault (core dumped)

正確的指針參數傳遞:

#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:字符串字面量修改

這個坑特別隱蔽,很多人不知道:

#include <stdio.h>

int main() {
    char *str = "Hello";  // 字符串字面量存在只讀區域
    
    printf("原字符串:%s\n", str);  // 輸出:Hello
    
    str[0] = 'h';  // ?? 試圖修改只讀內存,段錯誤!
    
    return 0;
}

// 輸出:原字符串:Hello
// Segmentation fault (core dumped)

正確做法:

#include <stdio.h>

int main() {
    char str[] = "Hello";  // 數組,可以修改
    // 或者 char str[10] = "Hello";
    
    printf("原字符串:%s\n", str);  // 輸出:Hello
    
    str[0] = 'h';  // 安全修改
    printf("修改后:%s\n", str);    // 輸出:hello
    
    return 0;
}

坑15:聯合體內存覆蓋陷阱

union使用不當也會造成意外:

#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("整數:%d\n", data.intVal);    // 輸出:100
    
    // 危險:strVal現在是垃圾值
    printf("字符串:%s\n", data.strVal);  // ?? 可能段錯誤!
    
    return 0;
}
/* 輸出:
字符串:Hello
整數:100
Segmentation fault (core dumped)
*/

安全使用聯合體:

#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;
    
    // 設置字符串
    data.type = TYPE_STRING;
    data.value.strVal = "Hello";
    
    if (data.type == TYPE_STRING) {
        printf("字符串:%s\n", data.value.strVal);
    }
    
    // 改為整數類型
    data.type = TYPE_INT;
    data.value.intVal = 100;
    
    if (data.type == TYPE_INT) {
        printf("整數:%d\n", data.value.intVal);
    }
    
    return 0;
}

坑16:函數指針未檢查

函數指針為NULL時調用會段錯誤:

#include <stdio.h>

void sayHello() {
    printf("Hello!\n");
}

int main() {
    void (*funcPtr)() = NULL;
    
    // 某些情況下可能給funcPtr賦值,某些情況下可能忘記
    if (rand() % 2 == 0) {
        funcPtr = sayHello;
    }
    
    funcPtr();  // ?? 如果funcPtr還是NULL,段錯誤!
    
    return 0;
}

安全調用函數指針:

#include <stdio.h>

void sayHello() {
    printf("Hello!\n");
}

void sayGoodbye() {
    printf("Goodbye!\n");
}

int main() {
    void (*funcPtr)() = NULL;
    
    // 根據條件設置函數指針
    if (rand() % 2 == 0) {
        funcPtr = sayHello;
    } else {
        funcPtr = sayGoodbye;
    }
    
    // 安全調用
    if (funcPtr != NULL) {
        funcPtr();
    } else {
        printf("函數指針為空,無法調用!\n");
    }
    
    return 0;
}

坑17:競態條件導致的段錯誤

多線程環境下的段錯誤更難調試:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int *global_ptr = NULL;

void* thread_func(void* arg) {
    if (global_ptr != NULL) {
        sleep(1);
        // ?? 另一個線程可能在這里釋放了global_ptr
        *global_ptr = 100;  // 可能段錯誤!
    }
    return NULL;
}

int main() {
    pthread_t thread;
    
    global_ptr = (int*)malloc(sizeof(int));
    
    pthread_create(&thread, NULL, thread_func, NULL);
    
    // 主線程釋放內存
    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("設置值成功:%d\n", *global_ptr);
    }
    return NULL;
}

int main() {
    pthread_t thread;
    
    global_ptr = (int*)malloc(sizeof(int));
    
    pthread_create(&thread, NULL, thread_func, NULL);
    
    // 等子線程完成再釋放!這才是關鍵
    pthread_join(thread, NULL);
    
    // 現在可以安全釋放了
    free(global_ptr);
    global_ptr = NULL;
    
    return 0;
}

避坑終極秘籍

看完這 17 個經典陷阱,相信你已經對段錯誤有了全新的認識!讓我總結幾個終極避坑秘籍:

內存管理黃金法則:

  • malloc必須配free - 有借有還,再借不難
  • free后立即置NULL - 防止野指針復活
  • 使用前必須檢查 - NULL指針是大敵
  • 邊界時刻要注意 - 數組越界是噩夢

指針使用終極口訣:

  • 初始化 - 聲明時就給個明確的值
  • 驗證 - 使用前檢查是否為NULL
  • 保護 - 操作時注意邊界和權限
  • 清理 - 用完立即釋放并置空

多級指針避坑技巧:

  • 二維數組 - 先分配行指針,再分配每行數據
  • 函數傳參 - 想改指針本身,就傳指針的地址
  • 結構體指針 - 內部的指針成員別忘了初始化

字符串操作安全守則:

  • 只讀區別碰 - 字符串字面量不能修改
  • 空間要充足 - strcpy前確保目標夠大
  • 邊界要檢查 - 防止緩沖區溢出

高級避坑技巧:

  • 聯合體慎用 - 記住類型,避免數據覆蓋
  • 函數指針檢查 - NULL函數指針不能調用
  • 多線程加鎖 - 共享資源要保護

記住這些,再配合調試工具(gdb、valgrind、AddressSanitizer),段錯誤再也不是你的攔路虎!

責任編輯:趙寧寧 來源: 跟著小康學編程
相關推薦

2025-06-09 10:05:00

C 語言指針編程

2019-07-10 09:12:20

程序員級別跳槽

2019-06-18 11:09:54

2010-10-18 11:39:41

程序員

2009-02-12 15:07:57

程序員創業經驗

2025-03-03 12:00:00

異步編程C#開發

2021-09-29 09:07:22

Docker 日志容器

2010-11-04 11:06:34

程序員

2019-07-02 09:30:31

程序員勞動陷阱

2020-11-09 07:38:19

RedisMySQL互聯網

2020-09-11 14:48:43

RedisMySQL數據

2018-03-15 10:21:50

程序員面試低級錯誤

2010-01-14 18:07:30

C++語言

2009-04-20 10:16:22

IT職場程序員面試

2025-03-12 01:35:00

同步編程模型

2019-03-21 14:45:37

C語言開發應用

2012-04-25 09:14:57

C++

2021-06-04 10:15:17

JavaSQL編程語言

2020-07-22 14:30:50

程序員財富螞蟻金服

2021-09-02 08:40:10

程序員錯誤
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜亚洲| 91精品国产综合久久久久 | 日韩欧美一区二区三区 | 欧美精品91 | 午夜av毛片 | 日本韩国欧美在线观看 | 久久久久精 | 日韩午夜在线观看 | 精品久久久久久久久久久 | 蜜桃在线视频 | 国产精品一区二区av | 日韩一二区 | 黑人巨大精品欧美一区二区免费 | 国产一区二区三区在线看 | 国产视频久久久 | 91精品国产高清一区二区三区 | 日日干干 | 免费在线一区二区 | 一区二区国产精品 | 日韩欧美网 | 久一精品 | 高清av电影 | 国产大学生情侣呻吟视频 | 国产 日韩 欧美 中文 在线播放 | 成人在线观看中文字幕 | 91久久精品国产91久久 | 日韩在线一区二区三区 | 女同久久另类99精品国产 | 欧美一级片在线观看 | 国产精品二区三区 | 午夜精品一区二区三区在线观看 | 精品小视频| www久久久| 黄视频网址| 日本韩国欧美在线观看 | 欧美日韩一二三区 | 中文字幕一区二区三区四区五区 | 欧美在线观看网站 | 日韩欧美中文在线 | 亚洲精品国产电影 | 欧美日韩不卡合集视频 |