系列第三篇,匯總 10 個極易踩坑卻鮮少被系統講授的 C 語言細節,建議收藏反芻。
__attribute__((noreturn)) 消除「控制路徑不可達」警告void Test() __attribute__((noreturn)); // 告訴編譯器:此函數永不返回
int main() {
int x; scanf("%d", &x);
if (x > 0) Test(); // 無警告,編譯器知 Test 不會返回
else return 0;
}
適用於 exit()、abort()、死循環線程函數等。GCC/Clang 專屬,MSVC 用 __declspec(noreturn)。
int i, a[10];
for (i = 0; i <= 10; i++) // 應為 i < 10
a[i] = 0;
原理:棧帧中 a 在高地址,i 在低地址。a[10] 越界寫入恰好覆蓋 i,將其置 0 → 循環永不結束。
**鐵律**:數組循環**嚴禁 <= n-1 寫成 <= n**,啟用 -fstack-protector-strong 讓編譯器插入金絲雀值檢測。
類型 推薦寫法 避免寫法 布爾 if (flag)/if (!flag)if (flag == TRUE)整數 if (val == 0)/if (val != 0)浮點 if (fabs(x - y) <= EPS)if (x == y)❌ 精度陷阱指針 if (ptr == NULL)if (!ptr)雖合法但語義較弱防誤賦值技巧:
if (NULL == ptr)、if (100 == i)—— 常量在左,誤寫=編譯報錯。5. 可變參數標準四步驟
#includedouble avg(int n, ...) { va_list ap; va_start(ap, n); double sum = 0; for (int i=0; i 陷阱:無類型/個數檢查,
va_arg(ap, int)取double會導致棧指針錯位,後續全亂。**必須約定終止條件**(計數、哨兵、格式串)。6. 僅四個運算符有規定求值順序
&&、||:先左後右(短路)?::先條件,再真/假分支,:先左後右,左值被丟棄反例:
y[i] = x[i++]——i++與y[i]求值順序未定義,結果不可預期。7. 數組結尾後一個元素:取址合法、讀值未定義
int a[10]; int *p = &a[10]; // ✅ 合法:指向結尾後位置(哨兵指針常用) int x = a[10]; // ❌ UB:讀取越界,編譯器極難檢測8. 字符數組 ≠ 字符串
- 字符數組:
char a[] = {'a','b','c'},無\0,長度固定- 字符串:
char *s = "abc"或char a[] = "abc",**以\0結尾**strlen只認\0,無\0當字符串用 → 讀越界sizeof(arr)含\0,strlen(arr)不含char arr1[] = {'a','b','\0','c','d'}; // sizeof=5, strlen=2 char arr2[] = "Hello"; // sizeof=6, strlen=5 char *p = "Hello"; // sizeof(p)=4/8, strlen=59. 引用初始化字面常量:會創建臨時對象
const int &r = 0; // 非 NULL 初始化! // 實際:創建臨時 int(0) → r 綁定臨時對象 → 臨時對象生命延長至 r 銷毀C++ 特有,C 無引用。理解臨時對象生命週期延長規則,避免誤以為綁定字面量本身。
10. 引用創建/銷毀不調用構造/析構函數
二進制層面引用本質是**指針常量**,編譯器自動處理解引用。
void f(int &r) { ... }實際傳遞地址,無構造/析構開銷。這 10 個細節,每個都能單獨引發生產事故。將其內化為「肌肉記憶」,是 C/C++ 程序員從「能跑」進化到「穩健」的必經之路。