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

C語言反直覺行為深度解析:六大經典陷阱與原理解析

     編輯:關於C

前言

C 語言看似簡單,實則暗藏許多「未定義行為」「實作定義行為」與「反直覺規則」。本文透過六個經典問題,從標準規範、編譯器實作、組合語言層面深度解析。

Q1:stdout/stderr 輸出順序不符代碼順序


while(1) {
    fprintf(stdout, "stdout.");
    fprintf(stderr, "stderr.");
    sleep(1);
}
// 實際輸出可能 stderr 先於 stdout 出現

原因

  • stdout 預設行緩衝 (終端) 或全緩衝 (管道/檔案)
  • stderr 預設無緩衝
  • 無換行符時,stdout 內容留在緩衝區,stderr 直接輸出

解決


setbuf(stdout, NULL);  // 關閉 stdout 緩衝
// 或
fflush(stdout);        // 手動刷新
// 或
fprintf(stdout, "stdout.\n");  // 換行觸發行緩衝刷新

Q2:printf 回傳值非預期


#define PRINT_D(x) printf(#x " is %d\n", (x))
char *str = "我";  // UTF-8 編碼,3 bytes
PRINT_D(printf(str));
// 輸出: printf(str) is 3  (非 1)

原因

  • printf 回傳實際輸出位元組數,非字元數
  • UTF-8 編碼「我」佔 3 位元組
  • 若檔案編碼為 GBK/Big5,中文佔 2 位元組,回傳 2

Q3:switch 區塊內變數初始化失效


int a = 1;
switch(a) {
    int b = 2;  // 永不執行!
    case 1: PRINT_D(b); break;
    default: PRINT_D(b); break;
}
// 輸出亂碼 (b 未初始化)

標準規定

C 標準:switch 語句體中,首個 case/default 標籤前的語句永遠不會被執行。

組合語言視角


// case 標籤編譯為直接跳轉
jne  L_case1    // 跳過初始化代碼
jmp  L_default
L_case1:
    // 直接執行 case 內容

修正


switch(a) {
    case 1: {
        int b = 2;  // 放入區塊內
        PRINT_D(b);
        break;
    }
    // 或
    int b = 2;  // 移至 switch 外
    switch(a) { ... }
}

Q4:sizeof 不計算副作用


int i = 1;
PRINT_D(sizeof(i++));  // 輸出 4 (int 大小)
PRINT_D(i);            // 輸出 1 (未遞增!)

原因

  • sizeof 是編譯期運算子 (VLA 除外)
  • 編譯器僅分析運算元型別,不生成求值代碼
  • 組合碼直接 mov $4, %ebx 推入堆疊

例外:VLA (Variable Length Array)


int n = 10;
sizeof(int[n++]);  // 這裡 n 會遞增!(C99 VLA 特例)

Q5:陣列下標交換律


char ch = 2["hello"];  // 等同 "hello"[2] → 'l'

原理


a[b]  定義為  *(a + b)
2["hello"]  →  *(2 + "hello")  →  *("hello" + 2)  →  "hello"[2]

本質

陣列存取完全等價於指標算術,編譯器無「陣列下標」概念,只有「指標偏移」。

Q6:格式化字串與引數型別不匹配


int d = 2;
PRINT_F(d);  // %f 對應 float/double
// 輸出亂碼 (如 0.000000)

原因

  • printf 為可變參數函數,無型別檢查
  • %f 期望 double (8 bytes),實際傳 int (4 bytes)
  • 讀取錯誤位元組組合 → IEEE 754 解讀為極小浮點數

IEEE 754 驗證工具


typedef union {
    float f;
    struct {
        unsigned int mantissa : 23;
        unsigned int exponent : 8;
        unsigned int sign     : 1;
    } bits;
} float_ieee754;

// 手動構造 -2.0
float_ieee754 v = {.bits = {.sign=1, .exponent=128, .mantissa=0}};
printf("%f\n", v.f);  // -2.000000

總結:C 語言核心認知修正

誤區正確認知
輸出即時顯示受緩衝模式控制,需手動刷新或換行
printf 回傳字元數回傳位元組數,多位元組字元需注意
switch 可聲明變數標籤前代碼死區,需用區塊或前置聲明
sizeof 執行運算元純編譯期型別運算 (VLA 除外)
a[b] 與 b[a] 不同完全等價,本質都是 *(a+b)
格式字串會檢查型別無檢查,必須程式員自行保證匹配
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved