| 段 | 全稱 | 內容 | 屬性 |
| RO | Read-Only | 程式碼、常數、唯讀資料 | 可執行、唯讀 |
| RW | Read-Write | 已初始化非零全域/靜態變數 | 可讀寫、需載入初值 |
| ZI | Zero-Initialized | 未初始化或初始化為 0 的全域/靜態變數 | 可讀寫、啟動時清零、無初值載入 |
疑問案例:未初始化全域變數為何進入 RW?
int aaa;
int bbb;
int main() {
aaa = 7;
bbb = 8;
return 0;
}
預期:aaa、bbb 未初始化 → 應屬 ZI 段(8 bytes)
實際:--bss_threshold=0 設定下,映像報告:
Program Size: Code=464 RO-data=268 RW-data=8 ZI-data=608
RW-data=8 表示 aaa、bbb 被放入 RW 段而非 ZI。
Map 檔案證據
符號表:
aaa 0x20000000 Data 4 main.o(.data)
bbb 0x20000004 Data 4 main.o(.data)
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x268, Max: 0x1000, ABSOLUTE)
Base Addr Size Type Attr Idx E Section Name Object
0x20000000 0x8 Data RW 11 .data main.o
確認兩變數位於 .data 區段(RW),而非 .bss(ZI)。
根因分析:--bss_threshold 機制
ARM 編譯器提供選項 --bss_threshold=size:
- 預設值:非零(通常為 4 或 8 位元組)
- 小於閾值的未初始化全域變數 → 歸入 RW (.data)
- 大於等於閾值 → 歸入 ZI (.bss)
設計考量:極小變數放 RW 而非 ZI,可避免 .bss 段碎片化,提高存取效率(.data 通常靠近程式碼區,快取友善)。
驗證實驗
// 測試 1:大陣列(超過閾值)
int big_array[1000]; // 4000 bytes > 閾值 → 應進 ZI
// 測試 2:明確初始化為 0
int zero_var = 0; // 標準 C 語意應進 ZI,但 KEIL 可能視為已初始化進 RW
// 測試 3:調整閾值
// --bss_threshold=0 // 強制所有未初始化進 ZI
// --bss_threshold=100 // 提高閾值
初始化非零全域變數同樣進 RW
int ccc = 5; // 明確非零初始化 → 必須進 RW(需載入初值 5)
符合預期:.data 區段包含所有需載入初值的變數,無論初值為何。
對嵌入式開發的影響
| 層面 | 影響 | 對策 |
| RAM 使用統計 | RW+ZI = 總資料 RAM,但 RW 需儲存初值於 ROM | Map 檔詳細分析 |
| ROM 佔用 | RW 區初值佔用 ROM 空間 | 大量小變數考慮合併結構體 |
| 啟動時間 | ZI 需清零,RW 需從 ROM 複製初值 | 權衡閾值設定 |
| 除錯觀察 | 變數地址分佈反映段歸屬 | 熟悉 Map 檔閱讀 |
最佳實踐建議
- 理解專案編譯選項:確認
--bss_threshold 設定值
- 大型緩衝區/陣列:自然落入 ZI,無需干預
- 關鍵小變數:若需確保 ZI,可改為
static int x = 0; 或調整閾值
- 記憶體受限系統:審視 RW 區大小,評估是否可將小變數合併或調整閾值
- 交叉編譯器差異:GCC 行為不同(通常未初始化全進 .bss),移植時注意
總結
- KEIL ARM 編譯器依
--bss_threshold 決定小型未初始化全域變數歸屬
- 預設閾值下,小變數(如單個 int)進 RW (.data) 而非 ZI (.bss)
- Map 檔
.data vs .bss 區段可確認變數實際歸屬
- 此機制為編譯器最佳化策略,非標準 C 語意強制
- 嵌入式開發者需理解此行為,準確評估 ROM/RAM 佔用
- 上一篇文章: C語言動態記憶體分配完全指南:malloc、free 原理與實戰應用
- 下一篇文章: C語言位域有號無號測試:有號位域負值陷阱實測
Copyright ©
程式碼之家 All Rights Reserved