C 語言看似簡潔,實則暗藏無數「未定義行為」「實現定義行為」與「邊界情況」。這十道題目來自經典技術博客,每一道都擊中核心語法語義盲區。敢不敢挑戰全對?
int x; return x == (1 && x);
答案:0(假)
x 未初始化,值不確定(垃圾值)。1 && x 先算:若 x!=0 結果為 1,否則 0。但 x 兩次出現**可能取不同值**(編譯器優化下),比較結果極大概率為 0。
教訓:永不使用未初始化變量,開啟 -Wuninitialized。
sizeof 返回無符號類型return ((1 - sizeof(int)) >> 32);
答案:0(非 -1)
sizeof(int) 類型為 size_t(無符號)。1 - 4u 發生無符號下溢,變成巨大正數(如 0xFFFFFFFFFFFFFFFD),右移 32 位後非 -1。
教訓:sizeof 結果參與運算時**顯式轉型為有符號**。
extern 在塊作用域的聲明int x = 5;
int f() { int x = 3; { extern int x; return x; } }
答案:5
塊內 extern int x 引用的是**文件作用域**的 x(值 5),而非外層函數的局部 x(值 3)。
教訓:extern 在塊內聲明**直接指向全局**,忽略中間作用域。
int (*pf)(void); int f(void) {
pf = &f; pf = ***f; pf(); (****pf)(); (***************f)(); }
答案:全部合法且等價
函數名隱式轉為指針;&f、f、*f、**f… 皆為函數指針類型;調用 pf() 自動解引用。
教訓:函數指針的「指針層級」在調用時會自動塌縮。
struct { int x; struct { int y, z; } nested; } i = { .nested.y = 5, 6, .x = 1, 2 };
答案:nested.y=2, nested.z=6
初始化器**按書寫順序**賦值,.nested.y=5 後緊跟的 6 給下一個成員 nested.z;隨後 .x=1 後的 2 給 x 後無成員,**實際賦給 nested.y**(因設計器已過 nested 但 y 是下一個?)實際規則極其複雜,**避免混用指定/非指定初始化器**。
typedef struct { char *key; char *value; } T1;
typedef struct { long type; char *value; } T3;
T1 a[] = { { "", ((char *)&((T3){1, (char *)1})) } };
int main() { T3 *pt3 = (T3*)a[0].value; return pt3->value; }
答案:1
(T3){1, (char*)1} 創建臨時結構體,取地址轉 char* 存入 a[0].value。取出後轉回 T3*,->value 即第二個成員 (char*)1,作為 int 返回 1。
教訓:複合字面量生命週期、指針類型懲罰、嚴格別名規則交織,極易出錯。
return ((int []){1,2,3,4})[1]; // 返回 2
C99 支持匿名陣列複合字面量,直接下標訪問。GCC 文檔稱合法但早期版本未實現。
static 函數與指針的塊級副本int foo() { static bar(); static (*pbar)() = bar; }
static bar() { return 1; }
static (*pbar)() = 0;
函數內 static bar() 是**聲明**(非定義),引用文件級 static bar() 定義。函數內 static pbar 初始化為該函數地址;文件級 pbar 另一個副本初始化為 0。兩個 pbar 互不干擾。
教訓:static 在函數內修飾函數聲明時含義特殊,極易誤解。
unsigned long foo() { return (unsigned long) - 1 / 8; }
答案:取決於編譯器
(unsigned long) - 1 / 8 解析為 ((unsigned long) - 1) / 8(巨大數)還是 (unsigned long) (-1 / 8)(0)?標準規定轉型優先級高於一元負號,即前者。但歷史編譯器可能不同。
教訓:**永遠加括號**:((unsigned long)(-1)) / 8。
int 返回類型 (C89 遺毒)早期 C 允許省略返回類型默認 int。現代編譯器嚴格模式下會報錯,但舊代碼仍存活。**始終顯式寫返回類型**。
這十個陷阱覆蓋了:初始化、整型提升、作用域、函數指針、指定初始化器、複合字面量、static 語義、運算符優先級、舊標準殘留。C 語言**不簡單**,精通它需要對標準條文、編譯器實現、硬件行為有深度理解。下次寫 C 代碼時,請多問一句:「這行代碼的標準語義是什麼?」