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

C語言字符串常量存儲機制深度解析:.rodata 區、編譯器行為與常見誤區

     編輯:關於C語言

前言:程序員認知的三個階段

  1. 懵懂階段:知道怎麼寫能跑,不知為何。
  2. 求知階段:搜尋資料,理解表面原理。
  3. 探索階段:結合工具鏈、內存佈局、標準文檔,驗證本質。

本文屬於第三階段產物,通過標準引用、反彙編、運行時驗證,釐清字符串常量的存儲真相。

C99 標準對字符串字面量的定義

語法:雙引號括起的多字節字符序列,如 "xyz"。
類型:char[](非 const char[],歷史兼容 K&R C)。
終止符:編譯階段 7 自動追加 \0。
修改行為:未定義行為 (UB)。
合併優化:相同字面量是否合併為單一實例,屬於實現定義。

為什麼類型是 char[] 而非 const char[]?

K&R C 無 const 關鍵字,ANSI C 引入 const 時為保持向後兼容,未修改字面量類型。但標準明確規定:修改字面量導致 UB。

實測驗證:字面量存儲在哪裡?

測試代碼


static char* sayHi = "Hi Fene!";
static char* sayHello = "Hello Fene!";
static char* dummyHi = "Hi Fene!";  // 與 sayHi 內容相同

void print() {
    printf("sayHi:%s, sayHello:%s, dummyHi:%s\n", sayHi, sayHello, dummyHi);
}

編譯與反彙編


gcc -m32 -Wall main.c -o main
objdump -s main

.rodata 區內容

Contents of section .rodata:
 80484f8  48692046 656e6521 00         Hi Fene!.
 8048501  48656c6c 6f204665 6e652100   Hello Fene!..
 804850e  48692046 656e6521 00         Hi Fene!.

關鍵發現:

  • 所有字面量位於 .rodata (唯讀數據段),載入內存後受頁表保護(不可寫)。
  • sayHi 和 dummyHi 指向不同地址(各自有一份 "Hi Fene!" 副本),未合併。
  • 每個字面量結尾均有 00 (\0)。

跨平台驗證結果

平台相同字面量是否合併存儲段
Linux x86 (GCC)否.rodata
Linux x64 (GCC)否.rodata
Linux ARM (GCC)否.rodata
Windows MSVC是 (/GF 默認開啟).rdata

結論:不可假設相同字面量共享地址,比較字符串內容必須用 strcmp,而非指針比較。

常見錯誤代碼與後果

1. 嘗試修改字面量


char *p = "i'm hungry!";
p[0] = 'I';  // UB:段錯誤

2. 缺少終止符的「偽字符串」


char a[3] = "abc";  // 合法,但無 \0
printf("%s", a);    // UB:讀越界
strcpy(b, a);       // UB

3. 循環中傳遞字面量被修改


while(1) print("Hello");  // 若 print 內部修改,下次循環看到的不是 "Hello"

最佳實踐建議

  • 接收字面量的函數參數應聲明為 const char*。
  • 需可修改字符串時,使用數組初始化:char buf[] = "hello";(存棧上,可寫)。
  • 嵌入式系統注意 .rodata 可能在 Flash,訪問速度與 RAM 不同。
  • 不要依賴編譯器合併字面量,邏輯上視為獨立實體。

總結

  • 字符串常量類型 char[],存儲於 .rodata 唯讀段,帶隱式 \0。
  • 修改導致 UB,現代 OS 會觸發段錯誤 (SIGSEGV)。
  • 相同內容不保證合併(GCC 默認不合併,MSVC 默認合併)。
  • 理解存儲機制,才能寫出安全、可移植的 C 代碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved