在嵌入式開發中,理解 C 程式編譯後的記憶體佈局至關重要。本文將透過 ARM 平台下的反組譯工具 arm-linux-objdump,深入剖析變量在不同記憶體段(.data、.bss、.rodata、.text)中的分佈規律,並對比 const 與 #define 的本質區別。
建立測試檔案 tmp.c,包含各類型變量:
#include <stdio.h>
#define VAR 0xFF
int a = 0; // 全域初始化為 0
static int b = 0; // 靜態全域初始化為 0
int c = 10; // 全域初始化非 0
static int d = 20; // 靜態全域初始化非 0
const int finalone = 10; // 全域常量初始化
const int final; // 全域常量未初始化
int main(void) {
char *hell = "hhhhhhh"; // 字串字面量
const int finaltwo = 50; // 局部常量
static int f = 0; // 靜態局部初始化為 0
static int k = 10; // 靜態局部初始化非 0
int aa; // 局部未初始化(優化掉)
int bb = 10; // 局部初始化非 0
printf("VAR = %d\n, finalone = %d, finaltwo = %d", VAR, finalone, finaltwo);
return 0;
}
編寫 Makefile:
CC=arm-linux-gcc CFLAGS += -march=armv7-a
執行 make tmp 生成 ELF 執行檔 tmp。
使用命令 arm-linux-objdump -D tmp > tmp.s 獲取完整反組譯列表,以下節選關鍵段落。
Disassembly of section .data: 00011020 <__data_start>: 00011028 <c>: 0000000a 0001102c <d>: 00000014 00011030 <k.1728>: 0000000a
結論:c、d、k 均為「全域或 static 修飾」且「初始值非 0」的變量,故置於 .data 段。
Disassembly of section .bss: 00011038 <a>: 0001103c <b>: 00011040 <f.1727>: 00011044 <final>:
結論:a、b、f、final 滿足「全域或 static」且「未初始化或初值為 0」,歸屬 .bss 段。
Disassembly of section .rodata: 0000848c <finalone>: 0000000a 00008490: 68686868 ... // "hhhhhhh" ASCII 碼
說明:字串字面量 "hhhhhhh" 及 const int finalone 存放於 .rodata,屬唯讀區域。
反組譯 main 函數片段:
000083fc: e3a010ff mov r1, #255 ; 0xff // #define VAR 0xFF 直接作為立即數嵌入指令 000083cc: e3a03032 mov r3, #50 ; 0x32 // const int finaltwo = 50 存入棧區
關鍵差異:
#define 宏在預處理階段直接文本替換,編譯時成為指令中的立即數,佔用 .text 段,每處使用皆產生一份拷貝。const 變量在編譯期確定值,存放於靜態區(.rodata 或棧),運行期僅有一份拷貝,且具備型別檢查。| 變量類型 | 記憶體段 | 條件 |
|---|---|---|
| 全域/靜態 + 初值非 0 | .data | 已初始化資料段 |
| 全域/靜態 + 未初始化或初值 0 | .bss | 未初始化資料段 |
| 字串常量、全域 const 初始化 | .rodata | 唯讀資料段 |
| #define 宏 | .text | 立即數嵌入指令碼 |
| 局部 const | 棧 | 運行期棧幀分配 |
透過反組譯可直觀掌握 C 語言變量的記憶體佈局,對嵌入式系統優化、除錯及安全性分析極具指導意義。理解 const 與 #define 的底層差異,有助於編寫更高效、可維護的程式碼。