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

C 語言運算符深度解析:復合賦值、逗號運算符、sizeof、副作用與序列點完全指南

     編輯:關於C語言

1. 復合賦值運算符:op= 不只是語法糖


i += 1;   // i 的值只計算一次
i = i + 1; // i 的值計算兩次(左值一次,右值一次)

實際差異示例:


int a[10];
int foo() { puts("foo called"); return 0; }

a[foo()] += 1;   // foo() 呼叫 1 次
a[foo()] = a[foo()] + 1;  // foo() 呼叫 2 次!

E1 op= E2 等價於 E1 = E1 op (E2),但 E1 只求值一次。這對有副作用的左值(如函數調用、++i、a[i++])至關重要。

2. 逗號運算符:expr1, expr2

  • 先求 expr1,丟棄其值,再求 expr2,整個表達式值為 expr2 的值。
  • 引入序列點:expr1 的所有副作用在 expr2 求值前完成。
  • 函數參數列表中的逗號是分隔符,非運算符,**無序列點**,求值順序未定。

int a = (1, 2, 3);  // a = 3
int b = 1, 2, 3;    // 錯誤:宣告語法,非表達式
f(a, (t=2, t+3), c); // 參數 2 使用逗號運算符,t=2 先執行,值為 5

3. sizeof 與 typedef

sizeof

  • 返回類型 size_t(無符號整數,<stddef.h>),不同平台寬度不同(32 位為 unsigned int,64 位為 unsigned long)。
  • 兩種形式:
    1. sizeof expression —— 不求值表達式(除 VLA)
    2. sizeof(type) —— 必須加括號
  • 不求值意味著:sizeof(i++) 不會修改 i。

typedef

  • 為類型起別名,不創建新類型。
  • typedef unsigned long size_t; → size_t 等價於 unsigned long。
  • 與 #define 區別:typedef 受作用域限制,參與類型檢查;#define 純文本替換。

4. Side Effect(副作用)與 Sequence Point(序列點)

副作用來源

  • 賦值運算符:= += -= *= /= %= &= ^= |= <<= >>=
  • 自增/自減:++ --
  • 函數調用(可能修改全局狀態、I/O、volatile 變量)。

序列點位置(C99)

  1. 完整表達式結尾(; 結束的表達式)。
  2. ||、&&、?:、,(逗號運算符)的左運算元求值後。
  3. 函數調用進入前(所有實參求值後)、返回後。
  4. 格式化 I/O 函數(printf、scanf)每個轉換規範處理後。

未定義行為(UB)判定規則

在兩個序列點之間:
① 同一標量對象被修改超過一次 → UB。
② 同一標量對象被修改後又被讀取,且讀取不用於確定該修改的值 → UB。

經典 UB 案例


a = (++a) + (++a) + (++a) + (++a);  // 修改 4 次,無序列點 → UB
i = i + 1;                            // OK:修改 1 次,讀取用於計算新值
a[i++] = i;                           // UB:i 被修改(++) 又被讀取(下標),無序列點
i = i++ + 1;                          // UB:修改(i++) 與賦值(=) 無序列點

合法讀寫模式


i = i + 1;        // 讀舊值 → 計算 → 寫新值,單一修改
*p++ = *q++;      // p++、q++ 副作用在賦值序列點前完成,合法
a[i] = i + 1;     // 讀 i 計算下標,讀 i 計算值,寫 a[i] —— 單一修改 a[i]

5. 邏輯運算符短路求值

  • a && b:先求 a,若假則不求 b,結果為假;若真則求 b,結果為 b 的真值。
  • a || b:先求 a,若真則不求 b,結果為真;若假則求 b,結果為 b 的真值。
  • 兩者均引入序列點,保證左側副作用完成。

6. 實戰建議

  1. 複雜表達式拆分為多條語句,利用 ; 序列點隔離副作用。
  2. 避免在函數參數、返回表達式、循環條件中寫副作用代碼。
  3. 開啟 -Wsequence-point、-fsanitize=undefined 捕獲 UB。
  4. 閱讀代碼時,標記每個變量的「最後修改點」與「下次讀取點」,確認中間有序列點。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved