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

C 語言未定義行為進階:返回局部變量地址、參數求值順序、副作用邊界

     編輯:關於C語言

案例 1:返回局部變量地址——懸空指針


int* inc(int val) {
    int a = val;
    a++;
    return &a;  // 錯誤:a 在棧上,函數返回後內存無效
}

int main() {
    int *val = inc(10);
    printf("%d", *val);  // UB:可能輸出 11、垃圾值、崩潰
}

正確做法:

  • 傳出參數:void inc(int *p) { (*p)++; }
  • 動態分配:int *p = malloc(sizeof(int)); *p = val+1; return p;(調用者負責 free)
  • 靜態變量(非線程安全):static int a; return &a;

案例 2:函數參數求值順序未規定


int a=10, b=20, c=30;
printf("%d..%d..%d", a+b+c, (b=b*2), (c=c*2));
// 輸出不可預測!
// GCC 右到左:60..40..60
// Clang 左到右:60..20..30
// 其他編譯器可能不同

鐵律:參數列表中不寫副作用表達式。拆分語句:


b *= 2; c *= 2;
printf("%d..%d..%d", a+b+c, b, c);  // 確定輸出 60..40..60

案例 3:多重副作用無序列點


printf("%d,%d,%d,%d", ++i, --i, i++, i--);  // UB:i 被修改 4 次,讀取多次
i = 1; i = i + 1;  // OK:單一修改,賦值運算符有序列點
int i = 1, j; j = i + (i = 2);  // UB:讀 i 與寫 i 無序列點隔離

案例 4:邏輯運算符的短路求值(有序列點)


int a = 0, b = 0;
if (a++ || b++) { }  // a=1, b=0(|| 序列點:左側真則右側不求值)
if (a++ && b++) { }  // a=2, b=1(&& 序列點:左側假則右側不求值)

&&、||、?:、,(逗號運算符)引入序列點,保證左運算元副作用完成後再求右運算元。

案例 5:逗號運算符 vs 函數參數分隔符


f(a, (t=2, t+3), c);  // 參數 2 使用逗號運算符:先 t=2,整個表達式值為 5
// 等價於:
t = 2;
f(a, t+3, c);

區別:函數參數列表中的逗號是分隔符,無序列點,求值順序未定;括號內的逗號是運算符,有序列點,左到右求值。

編譯器檢測與防禦

  • -Wall -Wextra -Wsequence-point (GCC/Clang) 可捕獲大多數 UB。
  • -fsanitize=undefined (UBSan) 運行時檢測。
  • 靜態分析工具:Cppcheck、Clang Static Analyzer、Coverity。

總結:避開 UB 的黃金法則

  1. 變量初始化後再使用。
  2. 同一表達式/語句中,對象至多修改一次。
  3. 讀取與修改同一對象時,必須有序列點隔離(賦值、邏輯運算、函數調用返回、語句結尾)。
  4. 函數參數純計算,無副作用。
  5. 指針不指向已釋放/離開作用域的內存。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved