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

C語言易忽略細節(三):noreturn屬性、棧佈局陷阱、分支優化、零值比較、可變參、求值順序、數組越界、字符數組vs字符串、引用初始化

     編輯:關於C語言

C語言易忽略細節(三):noreturn屬性、棧佈局陷阱、分支優化、零值比較、可變參、求值順序、數組越界、字符數組vs字符串、引用初始化

系列第三篇,匯總 10 個極易踩坑卻鮮少被系統講授的 C 語言細節,建議收藏反芻。

1. __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)。

2. 棧向下增長導致的數組越界死循環

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 讓編譯器插入金絲雀值檢測。

3. if/else 條件排序:真概率高的放前面

  類型推薦寫法避免寫法
  布爾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. 可變參數標準四步驟

#include 
double 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=5

9. 引用初始化字面常量:會創建臨時對象

const int &r = 0;  // 非 NULL 初始化!
// 實際:創建臨時 int(0) → r 綁定臨時對象 → 臨時對象生命延長至 r 銷毀

C++ 特有,C 無引用。理解臨時對象生命週期延長規則,避免誤以為綁定字面量本身。

10. 引用創建/銷毀不調用構造/析構函數

二進制層面引用本質是**指針常量**,編譯器自動處理解引用。
void f(int &r) { ... } 實際傳遞地址,無構造/析構開銷。

這 10 個細節,每個都能單獨引發生產事故。將其內化為「肌肉記憶」,是 C/C++ 程序員從「能跑」進化到「穩健」的必經之路。

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