理解 C 程序在記憶體中的分佈,是寫出高效、穩健代碼的前提。本文從作用域、生命週期、用戶空間視角、運行時段五個維度,系統闡述變量與代碼在記憶體中的安置規則。
程序啟動時分配,結束時釋放,地址固定,不動態申釋。包含四大段:
| 段名 | 內容 | 特徵 |
|---|---|---|
| 代碼段 | 機器指令、字串常量、const 全域/區域變量 | 唯讀,共享 |
| 只讀資料段 | const 修飾的全域/區域變量、字面常量 | 唯讀,編譯期確定 |
| 讀寫資料段 | 已初始化非零全域變量、static 區域變量 | 讀寫,有初值 |
| BSS 段 | 未初始化或初始化為 0 的全域/靜態區域變量 | 啟動時清零,不佔文件空間 |
注意:const char a[100]="ABCDEFG" 產生 100 位元組唯讀區;const char a[]="ABCDEFG" 僅 8 位元組(含 \0)。唯讀區通常需完全初始化。
函數調用時分配,返回時釋放。包含:
static 的區域變量)CPU 指令計數器(PC)指向此處順序執行機器碼。
存放運行期不變的數據:#define 常量、const 全域/區域變量、字串字面值。編譯期確定。
已初始化全域變量、static 區域變量。程序啟動分配,有初值,可讀寫。
未初始化全域變量、static 區域變量。啟動前不佔空間,載入時由系統清零。
運行時出現,程序員 malloc/free 手動管理。OS 可能在進程結束時回收未釋放內存。p = malloc(10) 分配的 10 位元組即在堆上。
運行時出現,編譯器自動管理。存放:函數內部動態變量、函數參數、返回值。動態增減:調用層次深則棧增長;大結構體傳參/返回佔用更多棧空間。
staticint f(int a) {
auto int b = 0; // 隱式 auto,棧上,每次調用重新初始化
static int c = 3; // 靜態區,僅初始化一次,保持值
b++; c++;
return a + b + c;
}
extern聲明外部變量/函數(定義在別處),不分配空間,僅擴展作用域至聲明處至文件末尾。跨文件共享全域變量:一個文件定義,其餘 extern 聲明。
// file1.c
int A = 13, B = -8;
// file2.c
extern int A, B; // 聲明,不定義
int max(int x, int y) { return x>y?x:y; }
int main() { printf("%d\n", max(A,B)); }
auto區域自動變量(含形參),棧上動態分配,調用時創建,返回時銷毀。auto 可省略,不寫即隱含自動儲存類。未初始化值不確定(髒值)。
register建議編譯器將區域自動變量/形參放入 CPU 寄存器,提高頻繁訪問效率。限制:寄存器數量有限,無法取地址(&),靜態區域變量不可用。現代編譯器優化極佳,通常無需手動指定。
| 分類 | 屬於 | 產生時機 | 釋放時機 |
|---|---|---|---|
| 代碼段、只讀段、讀寫段 | 靜態區 | 鏈接後 | 程序結束 |
| BSS 段 | 靜態區 | 程序初始化 | 程序結束 |
| 堆、棧 | 動態區 | 運行時按需 | 運行時釋放 |
映像文件(.out/.elf)含:代碼段、只讀段、讀寫段(稱 Section)。運行時進程含:上述三段 + BSS + 堆 + 棧(稱 Segment)。
const char ro[] = "this is read only data"; // 只讀數據段
static char rw_1[] = "this is global read write data"; // 讀寫數據段(全域)
char BSS_1[100]; // BSS 段(全域未初始化)
const char *ptrconst = "constant data"; // 字串在只讀段,ptrconst 在讀寫段/BSS
int main() {
short b; // 棧,2 位元組
char a[100]; // 棧,100 位元組
char s[] = "abcdefg"; // s 在棧(8 位元組),"abcdefg" 在只讀段(8 位元組)
char *p1; // 棧,指針大小
char *p2 = "123456"; // p2 在棧,"123456" 在只讀段
static char rw_2[] = "local rw"; // 讀寫數據段(局部靜態)
static char BSS_2[100]; // BSS 段(局部靜態未初始化)
static int c = 0; // 讀寫數據段(全域靜態初始化)
p1 = malloc(10); // 堆分配 10 位元組
strcpy(p1, "xxxx"); // "xxxx" 在只讀段(5 位元組含\0)
free(p1); // 釋放堆內存
return 0;
}
關鍵差異說明:
rw_1 無論是否 static 皆在讀寫段,static 僅影響跨文件可見性;rw_2 若無 static 則成棧上區域變量,語義全變。BSS_1(全域)全程序可見,BSS_2(局部靜態)僅函數內可見。BSS 段必須顯式指定大小(如 [100]),編譯器據此擴展 BSS 長度。