首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

C 語言 const 常量被指針修改?編譯器最佳化與未定義行為深度解析

     編輯:程式設計疑難解答

C 語言中宣告 const int a = 10;,直接賦值 a = 20 編譯報錯,但透過指針 *(int*)&a = 20 卻能「修改成功」並印出新值?本文揭示背後的編譯器最佳化、記憶體模型與未定義行為 (UB) 真相。

現象重現

const int a = 10;
printf("a = %d\n", a); // 輸出 10

int* p = (int*)&a;
*p = 20;

printf("a = %d\n", a);        // 輸出 10(或 20,視最佳化)
printf("*p = %d\n", *p);      // 輸出 20
printf("&a = %p, p = %p\n", &a, p); // 位址相同

核心解析:三個層面的真相

1. const 意味著「唯讀語意」,非「唯讀記憶體」

  • const int a 告訴編譯器:程式碼不應修改 a,編譯器可據此最佳化(如常數摺疊、置於 .rodata 段)。
  • C 標準 未保證 const 物件置於硬體唯讀記憶體(嵌入式系統除外)。

2. 編譯器最佳化導致「讀取舊值」

const int a = 10;
// 編譯器常數摺疊:所有讀取 a 直接替換為 10
printf("%d", a); // 實際機器碼:mov eax, 10 / call printf
// 即使記憶體位址 &a 內容已被指針改為 20,程式碼仍印出 10

這就是為何 a 仍印 10,而 *p 印 20 的原因:編譯器信任 const 承諾,直接內嵌常數值。

3. 透過指針修改 const 物件 = 未定義行為 (UB)

C11 標準 6.7.3/6:「若嘗試修改以 const 限定的物件,行為未定義。」

  • 編譯器有權假設 const 物件不變,進行積極最佳化。
  • 行為可能:程式崩潰(段錯誤、硬體保護)、印出舊值、印出新值、時間旅行(更早的操作受影響)。
  • 不可依賴、不可移植、嚴禁用於生產代碼。

記憶體佈局視覺化

.text (程式碼)        .rodata (唯讀資料)        .data/.bss (讀寫資料)        Stack/Heap
─────────────────      ──────────────────      ─────────────────────      ──────────
main:                   a = 10 (可能在這)        int b = 20;              ...
  mov eax, 10  ◄──┐                                         ▲
  call printf     │                                         │
                  │          *(int*)&a = 20  ──────────────┘ (UB:寫入 .rodata 可能觸發保護錯誤)
                  │
                  └── 常數摺疊:直接用 10,不讀取記憶體

正確做法:需要可修改就別加 const

// 1. 真正需要修改
int a = 10;
a = 20; // 合法

// 2. 函式參數唯讀保護(正確用法)
void print(const int* p) {
    // *p = 20; // 編譯錯誤:保護呼叫端資料不被修改
    printf("%d", *p);
}

// 3. 編譯期常數(C99 static const / C++ constexpr)
static const int MAX_SIZE = 100; // 有位址,但語意唯讀
#define MAX_SIZE 100             // 預處理替換,無位址
enum { MAX_SIZE = 100 };         // 編譯期常數,無位址

嵌入式系統特殊情況

若 const 變數配置在 Flash/ROM(透過 linker script 或 __attribute__((section(".rodata")))),硬體會阻擋寫入,指針修改直接觸發 HardFault/段錯誤。這是「const 進入唯讀記憶體」的少數場景。

總結

操作合法性結果
const int a = 10; a = 20;❌ 編譯錯誤語法層面阻擋
int* p = (int*)&a; *p = 20;⚠️ 編譯通過,但 UB行為不可預測,嚴禁使用
int a = 10; a = 20;✅ 完全合法正常可修改變數

const 是給編譯器與維護者的「契約」,而非硬體鎖。違反契約即進入 UB 風險區,現代編譯器會無情最佳化導致難以除錯的離譜行為。敬畏 const,善用它表達設計意圖,而非試圖繞過它。

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