C 語言變量在編碼、存儲、運算、輸出四個階段分別對應原始值、存儲狀態、實際值、輸出值。混淆這四者是數值計算 Bug 溫床。本文通過實驗徹底釐清概念邊界。
四種值形態定義
| 形態 | 階段 | 特點 | 例子 |
| 原始值 | 源碼字面量 | 程序員寫的值 | float f = 0.1; → 0.1 |
| 存儲狀態 | 內存位模式 | 二進制編碼 | 0x3DCCCCCD (IEEE 754) |
| 實際值 | 語義解讀後 | 類型確定的真實值 | 0.10000000149... |
| 輸出值 | printf 格式化 | 受格式字符控制 | %.6f → 0.100000 |
整數類型實驗
long i = 1, j = -1;
// 存儲狀態 (32位):
// i: 00000000 00000000 00000000 00000001 (0x00000001)
// j: 11111111 11111111 11111111 11111111 (0xFFFFFFFF)
// 運算: i + j
// 存儲狀態: 0x00000001 + 0xFFFFFFFF = 0x00000000
// 實際值: 0
printf 輸出值受格式控制
printf("%ld, %lu\n", j, j);
// 輸出: -1, 4294967295
// 同一存儲狀態,不同格式 → 不同輸出值
浮點類型實驗(關鍵!)
float fa = 0.1;
double fb = 0.1;
printf("%.20f, %.20f\n", fa, fb);
// VC6.0 輸出:
// 0.10000000149011612000
// 0.10000000000000001000
分析
- 原始值:均為 0.1
- 實際值:
fa ≈ 0.10000000149,fb ≈ 0.10000000000000001
- 誤差來源:二進制無法精確表示 0.1(循環小數)
- 輸出值:
%.2f → 0.10(四捨五入)
整型溢出實驗
short j = 50000; // short 範圍 -32768~32767
// 原始值: 50000
// 實際值: 實現定義(通常 -15536,補碼截斷)
// 存儲狀態: 0xC350
存儲狀態輸出技巧
int x = 0x12345678;
printf("%X\n", x); // 12345678 (十六進制)
printf("%o\n", x); // 2215054170 (八進制)
// %X/%o 直接輸出存儲狀態位模式,不經語義解讀
核心結論
| 屬性 | 存儲狀態 | 實際值 | 輸出值 |
| 唯一性 | 固定 | 固定 | 多樣(隨格式) |
| 依賴類型 | 否 | 是 | 是 (格式字符) |
| 運算基礎 | 是 (ALU 直接操作位) | 否 | 否 |
| 可觀測性 | 需調試器/特殊格式 | 需高精度輸出 | 直接可見 |
最佳實踐
- ✅ 理解 運算基於存儲狀態/實際值,非原始值/輸出值
- ✅ 浮點比較用 epsilon:
fabs(a-b) < EPS
- ✅ 整型溢出:用更寬類型或檢查邊界
- ✅
printf 格式必須與實際類型匹配
- ✅ 調試時用
%X 查看位模式,用高精度 %.20f 查看浮點實際值
- ✅ 區分「原始值」(源碼)與「實際值」(語義)
常見誤區
// ❌ 以為 0.1 能精確表示
if(f == 0.1) ... // 危險!
// ✅ 正確
if(fabs(f - 0.1) < 1e-6) ...
// ❌ 以為 printf 顯示的就是實際值
printf("%.2f", 0.100000001); // 顯示 0.10 ≠ 實際值
// ❌ 以為存儲狀態 = 實際值
// 同一 0xFFFFFFFF 可為 -1 (有符號) 或 4294967295 (無符號)