首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 語言記憶體分配陷阱:棧上陣列 vs 堆上 malloc 實測對比

     編輯:關於C

面試真題回顧

某技術社團幹部面試遇到的經典記憶體分配題目:兩段外觀相似的程式碼,執行結果截然不同。核心差異在於 allocmem 函數內部採用區域陣列還是 malloc 分配記憶體。

程式碼對比

版本 A:區域陣列(棧上分配)

void allocmem(char **p) {
    char r[10] = {1};  // 區域陣列,存放於棧
    *p = r;            // 將棧位址賦給輸出指標
}

int main() {
    char *m = NULL;
    allocmem(&m);
    if (m) {
        for (int i = 0; i < 10; i++)
            printf("m[%d] = %d\n", i, m[i]);  // 輸出亂碼或崩潰
    }
}

執行結果:印出的值非預期的 1,而是隨機亂碼(見圖片)。

版本 B:malloc(堆上分配)

void allocmem(char **p) {
    *p = malloc(10);          // 堆上分配
    memset(*p, 1, 10);        // 初始化為 1
}

int main() {
    char *m = NULL;
    allocmem(&m);
    if (m) {
        for (int i = 0; i < 10; i++)
            printf("m[%d] = %d\n", i, m[i]);  // 正確輸出十個 1
    }
    free(m);  // 記得釋放!
}

執行結果:正確印出十個 1(見圖片)。

根本原因深度解析

特性版本 A:區域陣列版本 B:malloc
儲存區域棧堆
生命週期函數返回即銷毀手動 free 前持續有效
位址有效性函數結束後成「懸空指標」跨函數有效
記憶體殘留棧幀被後續呼叫覆寫資料完整保留

記憶體佈局示意

版本 A 執行流程:
main() 呼叫 allocmem()
  ├─ allocmem() 棧幀建立
  │   ├─ r[10] 分配於棧頂
  │   └─ *p = &r[0]  ← 回傳棧位址
  └─ allocmem() 返回,棧幀銷毀,r[10] 記憶體標記為可重用
main() 繼續執行,m 指向已失效位址 → 未定義行為

版本 B 執行流程:
main() 呼叫 allocmem()
  └─ malloc(10) 向作業系統申請堆記憶體
     └─ 位址存放於 *p,函數返回後堆區塊仍屬進程所有
main() 透過 m 正常存取 → 正確結果

避坑指南與最佳實踐

  1. 永遠不要回傳區域變數位址:編譯器通常會警告「returning address of local variable」。
  2. 跨函數共享記憶體三種模式:
    • 呼叫端分配,被呼叫端填充:void func(char *buf, size_t len)
    • 被呼叫端 malloc,呼叫端 free(本文版本 B)
    • 靜態/全域緩衝區(線程不安全,慎用)
  3. 善用工具偵測:valgrind、AddressSanitizer (`-fsanitize=address`) 能精準捕捉 Use-after-free、Stack-use-after-scope 等錯誤。
  4. 現代 C++ 建議:改用 std::vector、std::string、std::unique_ptr 等 RAII 容器,從語言層面杜絕手動記憶體管理錯誤。

總結

這道題目揭示了 C 語言最核心也最危險的特性:手動記憶體管理與作用域生命週期。理解棧/堆差異、識別懸空指標,是從「能寫出跑得動的程式」進階為「寫出健壯、可維護系統程式」的必經之路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved