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

C 語言記憶體區域劃分與動態管理:堆、棧、靜態區全解析

     編輯:關於C語言

一、五大記憶體區域職責

區域管理方式存放內容生命週期
棧區編譯器自動分配/釋放區域變數、函數參數、返回位址、暫存器溢出函數呼叫期間
堆區程式設計師手動 malloc/free動態分配物件、大型緩衝區手動釋放前持續存在
靜態/全域區連結器配置,程式啟動初始化static 變數、全域變數、字串常值程式整個執行期間
常數區唯讀記憶體段字串字面值、const 全域變數程式執行期間,不可修改
程式碼區唯讀CPU 指令、函數機器碼程式執行期間

二、動態記憶體分配核心 API

malloc:原始位元組分配

void *malloc(size_t size);
// 回傳對齊後的位址,內容未初始化(隨機值)
int *p = malloc(10 * sizeof(int)); // 分配 10 個 int 空間

calloc:歸零分配

void *calloc(size_t nmemb, size_t size);
// 分配 nmemb * size 位元組並全部設為 0
int *p = calloc(10, sizeof(int)); // 等效 malloc + memset 0

realloc:調整大小

void *realloc(void *ptr, size_t new_size);
// 1. 原地擴展(位址不變)若後方有足夠空閒空間
// 2. 新分配 + 複製舊資料 + 釋放舊塊(位址改變)
// 3. new_size=0 等效 free(ptr)
int *p = malloc(4 * sizeof(int));
p = realloc(p, 100 * sizeof(int)); // 必須用回傳值更新指標!

三、記憶體操作函數族

函數用途關鍵差異
memset(ptr, val, n)將 n 位元組設為 val(以 unsigned char 逐位元組填充)逐位元組填充,memset(p, 1, 4) 使每個 int 變 0x01010101 ≠ 1
memcpy(dst, src, n)複製 n 位元組,來源與目的不可重疊高效、不檢查重疊
memmove(dst, src, n)複製 n 位元組,安全處理重疊內部判斷方向複製,稍慢於 memcpy
memcmp(s1, s2, n)比較前 n 位元組,回傳 <0, 0, >0按 unsigned char 比較

四、完整實戰範例:動態字串陣列管理

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

#define INIT_CAP 4
#define GROW_FACTOR 2

typedef struct {
    char **data;
    size_t len;
    size_t cap;
} StrArray;

StrArray *sa_create() {
    StrArray *sa = malloc(sizeof(StrArray));
    sa->data = malloc(INIT_CAP * sizeof(char*));
    sa->len = 0; sa->cap = INIT_CAP;
    return sa;
}

void sa_push(StrArray *sa, const char *str) {
    if (sa->len == sa->cap) {
        sa->cap *= GROW_FACTOR;
        sa->data = realloc(sa->data, sa->cap * sizeof(char*));
    }
    sa->data[sa->len] = strdup(str); // strdup = malloc + strcpy
    sa->len++;
}

void sa_destroy(StrArray *sa) {
    for (size_t i = 0; i < sa->len; i++) free(sa->data[i]);
    free(sa->data);
    free(sa);
}

int main() {
    StrArray *sa = sa_create();
    sa_push(sa, "Hello");
    sa_push(sa, "World");
    sa_push(sa, "C Language");
    
    for (size_t i = 0; i < sa->len; i++)
        puts(sa->data[i]);
    
    sa_destroy(sa);
    return 0;
}

五、避坑指南:十大記憶體錯誤

  1. 記憶體洩漏:malloc 無對應 free,長期運行耗盡記憶體。
  2. 重複釋放:free(p); free(p); → 未定義行為。釋放後立即 p = NULL。
  3. 使用已釋放記憶體:free(p); *p = 1; → Use-After-Free,安全性風險極高。
  4. 緩衝區溢位:寫入超過分配大小,破壞堆元資料導致後續 malloc/free 崩潰。
  5. 未檢查回傳值:分配失敗回傳 NULL,直接解參考導致 Segfault。
  6. 型別大小計算錯誤:malloc(n * sizeof(int*)) 應為 sizeof(int)。
  7. 結構體柔性數組分配:malloc(sizeof(Struct) + len) 忘記加資料長度。
  8. memset 誤用於非 0/-1 整數:逐位元組填充導致整數值非預期。
  9. realloc 未保存回傳值:位址可能改變,舊指標失效。
  10. 跨模組釋放:DLL 邊界傳遞指標並在另一模組 free,堆管理器不共用導致崩潰。

六、現代最佳實踐

  • 使用 calloc 分配結構體陣列,自動歸零指標成員。
  • 封裝分配/釋放為 create/destroy 配對函數,隱藏細節。
  • 嚴格遵循「誰分配誰釋放」所有權原則。
  • 啟用 AddressSanitizer (-fsanitize=address) 開發期偵測記憶體錯誤。
  • 大型專案考慮記憶體池分配器減少系統呼叫開銷。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved