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

C/C++ 記憶體佈局深度解析:程式在記憶體中如何組織

     編輯:關於C語言

前言:為什麼要理解記憶體佈局?

理解 C/C++ 程式在虛擬記憶體中的分佈,是掌握**指標運算、動態記憶體管理、緩衝區溢位、栈溢位、效能優化、逆向工程、嵌入式開發**的基石。本文系統梳理經典的 **Linux x86/x64 進程虛擬地址空間佈局**(高地址 → 低地址)。

進程虛擬地址空間全景圖

高地址 (0xFFFF...FFFF)
+------------------------+
|   內核空間 (1GB/2GB)   |  用戶程式不可直接訪問,系統呼叫陷入
+------------------------+
|      用戶空間          |
|  +------------------+  |
|  |    棧 Stack      |  |  ↓ 向下增長 (高→低)
|  |  (區域變數、      |  |
|  |   呼叫框架、      |  |
|  |   返回地址)      |  |
|  +------------------+  |
|  |        ↓         |  |  未映射區域 (Guard Page)
|  |        ↑         |  |
|  |    堆 Heap       |  |  ↑ 向上增長 (低→高)
|  |  (malloc/new、   |  |
|  |   動態記憶體)    |  |
|  +------------------+  |
|  |  BSS 段          |  |  未初始化全域/靜態變數 (編譯器置 0)
|  |  (.bss)          |  |
|  +------------------+  |
|  |  數據段 Data     |  |  已初始化全域/靜態變數
|  |  (.data)         |  |
|  +------------------+  |
|  |  程式碼段 Text   |  |  唯讀:指令、常數、字面值
|  |  (.text/.rodata) |  |
+------------------------+
低地址 (0x0000...0000)

五大段詳細解析

1. 程式碼段 — .text / .rodata

  • 內容:CPU 執行的機器指令、字串常數、const 修飾的全域變數 (編譯器優化後)、函數本體。
  • 屬性:**唯讀、可共享**。多進程執行同一程式時,記憶體中只保留一份 `.text` 映射。
  • 保護:嘗試寫入觸發 SIGSEGV (Segmentation Fault)。

2. 初始化資料段 — .data

  • 內容:顯式初始化非 0 的全域變數、static 區域變數、static 類成員變數。
    例:int g_val = 42; static int s_val = 10;
  • 屬性:**讀寫、私有**。每進程一份副本 (Copy-on-Write)。
  • 儲存:可執行檔中佔用空間,載入時從磁碟映射。

3. 未初始化資料段 — .bss (Block Started by Symbol)

  • 內容:未顯式初始化** 或 **初始化為 0** 的全域/靜態變數。
    例:int g_arr[1000]; static int counter; int g_zero = 0;
  • 屬性:讀寫、私有。**不佔可執行檔空間**——載入時由核心**自動清零**映射頁面。
  • 優勢:大幅減小執行檔體積。int big[1000000] = {0}; 在 `.bss` 只佔幾 byte 元資料,在 `.data` 會佔 4MB 檔案體積。

4. 堆 — Heap

  • 管理:由 malloc/free、new/delete、brk/mmap 系統呼叫動態管理。
  • 增長方向:向**高地址**擴展 (呼叫 sbrk/mmap 向核心申請)。
  • 碎片化:頻繁分配釋放產生外部碎片,分配器 (glibc malloc、jemalloc、tcmalloc) 用 bins/arenas 緩解。
  • 常見問題:記憶體洩漏、重複釋放、使用後釋放、緩衝區溢位、雙重釋放。

5. 棧 — Stack

  • 管理:編譯器自動管理,函數呼叫/返回 隱式推疊/彈疊。
  • 內容:區域變數 (非 static)、函數參數、返回地址、暫存器保存、堆疊幀框架。
  • 增長方向:向**低地址**增長 (x86/x64 `push` 遞減 `rsp`/`esp`)。
  • 大小限制:Linux 預設 **8MB** (`ulimit -s` 可調),嵌入式可能只有 64KB~256KB。
  • 栈溢位:遞迴過深、大型區域陣列 int a[1000000]、無限遞迴。

關鍵概念深度剖析

虛擬記憶體 vs 物理記憶體

  • 每進程擁有**獨立 4GB (32位) / 256TB (48位 x64)** 虛擬地址空間。
  • MMU (Memory Management Unit) 透過**頁表** 將虛擬頁映射到物理頁框。
  • 共享庫 (`.so`/`.dll`) 在虛擬空間不同進程映射到**同一物理頁** (Copy-on-Write)。
  • 分頁交換:閒置頁面換出到 swap 分區,使用時換入。

記憶體對齊與結構體佈局

struct Foo {
    char c;      // offset 0, size 1
    // 3 bytes padding (alignment 4)
    int i;       // offset 4, size 4
    // 4 bytes padding (alignment 8 for double)
    double d;    // offset 16, size 8
}; // sizeof(Foo) = 24, alignof(Foo) = 8
  • 編譯器插入 **padding** 滿足成員對齊需求 (通常等於成員大小)。
  • 結構體總大小為**最大對齊數的倍數**,陣列連續存放時每元素對齊。
  • `#pragma pack(n)` / `__attribute__((packed))` 可壓縮佈局,但可能導致非對齊訪問效能下降或硬體異常 (如 ARM 某指令)。

命令列參數與環境變數位置

位於**棧頂附近** (高地址端),棧向下增長時會「壓過」它們:

argc, argv[], envp[] → 字串實際內容 → 輔助向量 (AT_*)

實戰驗證:打印各段地址

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

int g_data = 42;        // .data
int g_bss;              // .bss
const char *g_ro = "ro"; // .rodata (指標在 .data, 字串在 .rodata)

void print_segments() {
    int local = 99;     // stack
    int *heap = malloc(4); // heap
    
    printf("Code (.text)   : %p (main)\n", (void*)main);
    printf("RO Data (.rodata): %p\n", (void*)g_ro);
    printf("Data (.data)   : %p\n", (void*)&g_data);
    printf("BSS (.bss)     : %p\n", (void*)&g_bss);
    printf("Heap           : %p\n", (void*)heap);
    printf("Stack          : %p\n", (void*)&local);
    
    free(heap);
}

int main() {
    print_segments();
    return 0;
}

典型輸出 (Linux x64 ASLR 開啟):

Code (.text)    : 0x55a3b1c3d1a9
RO Data (.rodata): 0x55a3b1c3e004
Data (.data)    : 0x55a3b1e41014
BSS (.bss)      : 0x55a3b1e41040
Heap            : 0x55a3b3c5e2a0
Stack           : 0x7ffc8b3a9b3c

觀察:Code/Data/BSS 相鄰 (同一映射區域),Heap 在中間偏上,Stack 在用戶空間頂端附近。**ASLR (Address Space Layout Randomization)** 使每次運行基址不同,增強安全性。


嵌入式/裸機環境差異

特性Linux/Windows (OS)裸機/RTOS (無 MMU)
虛擬記憶體有 (MMU)無 (物理地址 = 虛擬地址)
堆動態向核心申請靜態記憶體池 (linker script 定義)
棧每執行緒一個, 動態增長固定位置/大小 (linker script 定義)
.bss 初始化核心載入時清零啟動代碼 (crt0) 手動清零
保護機制頁表權限 (R/W/X)MPU (Memory Protection Unit) 可選

總結: C/C++ 記憶體佈局不是抽象理論,而是直接決定了**指標有效性、物件生命週期、效能特徵、安全邊界**。掌握「五大段」分佈、增長方向、管理方式、對齊規則,能讓你在面對 Segment Fault、記憶體洩漏、緩衝區溢位、效能瓶頸時,直擊本質、精準定位、從容解決。建議配合 `gdb`、`pmap`、`/proc/pid/maps`、`objdump -h` 等工具實地觀察驗證。

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