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

誰說 C 語言簡單?十個讓你大跌眼鏡的經典陷阱題

     編輯:關於C語言

前言

C 語言看似簡潔,實則暗藏無數「未定義行為」「實現定義行為」與「邊界情況」。這十道題目來自經典技術博客,每一道都擊中核心語法語義盲區。敢不敢挑戰全對?

陷阱 1:未初始化變量的「默認值」

int x; return x == (1 && x);

答案:0(假)
x 未初始化,值不確定(垃圾值)。1 && x 先算:若 x!=0 結果為 1,否則 0。但 x 兩次出現**可能取不同值**(編譯器優化下),比較結果極大概率為 0。
教訓:永不使用未初始化變量,開啟 -Wuninitialized。

陷阱 2:sizeof 返回無符號類型

return ((1 - sizeof(int)) >> 32);

答案:0(非 -1)
sizeof(int) 類型為 size_t(無符號)。1 - 4u 發生無符號下溢,變成巨大正數(如 0xFFFFFFFFFFFFFFFD),右移 32 位後非 -1。
教訓:sizeof 結果參與運算時**顯式轉型為有符號**。

陷阱 3:extern 在塊作用域的聲明

int x = 5;
int f() { int x = 3; { extern int x; return x; } }

答案:5
塊內 extern int x 引用的是**文件作用域**的 x(值 5),而非外層函數的局部 x(值 3)。
教訓:extern 在塊內聲明**直接指向全局**,忽略中間作用域。

陷阱 4:函數指針的「多重解引用」等價性

int (*pf)(void); int f(void) {
    pf = &f; pf = ***f; pf(); (****pf)(); (***************f)(); }

答案:全部合法且等價
函數名隱式轉為指針;&f、f、*f、**f… 皆為函數指針類型;調用 pf() 自動解引用。
教訓:函數指針的「指針層級」在調用時會自動塌縮。

陷阱 5:指定初始化器的順序陷阱 (C99)

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 是下一個?)實際規則極其複雜,**避免混用指定/非指定初始化器**。

陷阱 6:複合字面量與指針運算的「類型懲罰」

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。
教訓:複合字面量生命週期、指針類型懲罰、嚴格別名規則交織,極易出錯。

陷阱 7:複合字面量作為陣列下標

return ((int []){1,2,3,4})[1]; // 返回 2

C99 支持匿名陣列複合字面量,直接下標訪問。GCC 文檔稱合法但早期版本未實現。

陷阱 8: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 在函數內修飾函數聲明時含義特殊,極易誤解。

陷阱 9:轉型優先級與負號結合

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。

陷阱 10:隱式 int 返回類型 (C89 遺毒)

早期 C 允許省略返回類型默認 int。現代編譯器嚴格模式下會報錯,但舊代碼仍存活。**始終顯式寫返回類型**。

總結

這十個陷阱覆蓋了:初始化、整型提升、作用域、函數指針、指定初始化器、複合字面量、static 語義、運算符優先級、舊標準殘留。C 語言**不簡單**,精通它需要對標準條文、編譯器實現、硬件行為有深度理解。下次寫 C 代碼時,請多問一句:「這行代碼的標準語義是什麼?」

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