可執行文件存儲結構(靜態視圖)
GCC 編譯生成的 ELF 文件包含三大段:
| 段 | 含義 | 示例內容 | 屬性 |
| .text (代碼段) | 機器指令 | 函數體、字符串常量 | 只讀、可執行 |
| .data (數據段) | 已初始化全局/靜態變量 | int a = 10; | 讀寫 |
| .bss (未初始化數據段) | 未初始化全局/靜態變量 | int b; static int c; | 讀寫、運行時清零 |
驗證實驗
// Hello.c
#include <stdio.h>
int main() { printf("Hello World\n"); return 0; }
size Hello 輸出:
text data bss dec hex filename
1184 552 8 1744 6d0 Hello
- 添加
const int i = 5; → text +4(常量入代碼段)
- 添加
int a = 2; → data +4(初始化全局變量)
- 添加
int b; → bss +4(未初始化全局變量)
進程虛擬地址空間(動態視圖)
高地址
+------------------+
| 內核空間 (1GB) | 用戶態不可訪問
+------------------+
| 用戶棧 (向下增長) | 局部變量、函數調用幀
| ↓ |
| |
| ↑ |
| 用戶堆 (向上增長) | malloc/free/new/delete
+------------------+
| .bss 段 | 未初始化全局/靜態
+------------------+
| .data 段 | 初始化全局/靜態
+------------------+
| .text 段 | 代碼、只讀數據
+------------------+
低地址 (0x08048000 典型起始)
五大區域詳細說明
| 區域 | 管理方式 | 生命週期 | 典型內容 |
| 代碼區 | 加載器映射 | 進程整個運行期 | 二進制指令、字面量常量 |
| 全局/靜態區 | 加載器分配 | 進程整個運行期 | 全局變量、static 變量 |
| 棧區 | 編譯器自動管理 | 函數調用期間 | 局部變量、參數、返回地址、寄存器保存 |
| 堆區 | 程序員手動管理 | malloc 到 free | 動態分配內存 |
| 保留區 | 內核 | -- | 內核數據結構、設備映射等 |
變量存儲類型完整表
| 聲明 | 存儲區 | 作用域 | 鏈接屬性 | 初始值 |
int a; (文件級) | .bss | 文件/全局 | 外部 | 0 |
int a = 1; (文件級) | .data | 文件/全局 | 外部 | 1 |
static int a; (文件級) | .bss | 文件 | 內部 | 0 |
static int a = 1; (文件級) | .data | 文件 | 內部 | 1 |
int a; (函數內) | 棧 | 塊 | 無 | 不定值 |
static int a; (函數內) | .bss/.data | 塊 | 無 | 0/初始值 |
const int a = 1; | .text/.rodata | 按位置定 | 內部(C)/外部(C++) | 1 |
動態內存管理最佳實踐
// 1. 檢查返回值
int *p = malloc(n * sizeof(int));
if (!p) { perror("malloc"); exit(EXIT_FAILURE); }
// 2. 釋放後置 NULL 防野指針
free(p); p = NULL;
// 3. 靈活數組成員 (C99)
struct { size_t len; int data[]; } *vec = malloc(sizeof(*vec) + n * sizeof(int));
// 4. 對齊分配 (C11)
void *p = aligned_alloc(64, size); // 64字節對齊,適合 SIMD
// 5. 內存池/分配器:高頻小塊分配場景自定義管理
常見錯誤與調試
- 棧溢出: 大局部數組/深度遞歸 → 改用靜態/堆分配或增大棧 (
ulimit -s/鏈接器選項)
- 堆損壞: 越界寫入/雙重釋放/使用後釋放 → AddressSanitizer (
-fsanitize=address)、Valgrind
- 內存洩漏: 分配無釋放 → 靜態分析、運行時檢測工具
- 野指針: 釋放後訪問/未初始化指針 → 釋放即置 NULL、聲明即初始化