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

C 語言自增自減運算符副作用陷阱全景分析:宏、邏輯運算、指針綜合篇

     編輯:關於C語言

核心原則

同一序列點內,對同一標量對象多次修改,或修改後讀取值(非為了確定新值),行為未定義(UB)。

序列點:完整表達式末尾、`&&`/`||`/`?:`/`,`(逗號運算符)、函數調用前後。

第一部分:宏展開導致的多重求值


#define BAND1(x) (((x) >= 5) && ((x) <= 10) ? (x) : 0)
#define BAND2(x) (((x) >= 5) && ((x) <= 5) ? (x) : 0)

int i = 5;
BAND1(++i);  // 宏展開:((++i)>=5) && ((++i)<=10) ? (++i) : 0
// i 被修改 3 次!結果:GCC 下返回 8,i 變 8
// BAND2(++i):條件 (x>=5)&&(x<=5) 只在 x==5 真,但 x 被修改多次 → UB

教訓:宏參數若帶副作用,展開後可能被求值多次。絕不在宏參數中使用 `++`/`--`/賦值/函數調用。

第二部分:邏輯運算符的短路與副作用順序

`&&` `||` 引入序列點:左側求值完成(含副作用)→ 序列點 → 右側求值。


int i = 4, a;
a = (i++ >= 5);           // i=4 -> 假(0),然後 i=5
a = (i++ >= 5) + (i++ >= 5); 
// 第一項:i=5 -> 假(0),i=6;第二項:i=6 -> 真(1),i=7;a=0+1=1
a = (i++ >= 4) && (i++ >= 5);
// 第一項:i=7 -> 真(1),i=8;短路?否,繼續;第二項:i=8 -> 真(1),i=9;a=1

關鍵區別:算術 `+` vs 邏輯 `&&`

表達式副作用順序原因
`(i++) + (i++)`未指定順序,UB`+` 無序列點,兩側求值順序未定
`(i++) && (i++)`左→右,有序列點`&&` 保證左側完整求值後再右側

第三部分:指針與自增結合的優先級與結合性

後綴 `++`、前綴 `++`、解引用 `*` 同優先級,**右結合**。


char *p = "hello";
char c;

c = *p++;   // 等價 *(p++):取 *p('h'),p 指向 'e'
c = *++p;   // 等價 *(++p):p 指向 'l',取 *p('l')
c = ++*p;   // 等價 ++(*p):*p('l') -> 'm',取值 'm'

結合性推導表

表達式分組操作順序結果
`*p++``*(p++)`1.p++ 2.*old_p舊值,p 後移
`*++p``*(++p)`1.++p 2.*new_p新值,p 先移
`++*p``++(*p)`1.*p 2.++value修改所指值,p 不動
`(*p)++``(*p)++`1.*p 2.++value同上,返回舊值

第四部分:綜合測試案例(GCC 行為,不可移植)


int i = 1, a;
a = (i--) && (i);      // i=1->真,i=0;右側 i=0->假;a=0,i=0
a = (i) && (i--);      // i=0->假,短路,i 不遞減;a=0,i=0
a = (i) && (i--) && (i); // i=0->假,短路;a=0
a = (i--) + (i);       // UB:+ 無序列點,但 GCC:取舊值 0+0=0,i=-1?實測 i=0

最佳實踐總結

  1. 拒絕花式一行式:`a = (i++) + (++i) * (i--)` 寫不出可維護代碼
  2. 宏參數無副作用:`#define MAX(a,b) ((a)>(b)?(a):(b))` 若傳 `MAX(i++, j++)` 會雙增
  3. 指針運算顯式括號:`(*p)++` 而非 `*p++`(除非確定要移動指針)
  4. 循環變量單一修改點:`for (int i=0; i
  5. 啟用編譯器警告:`-Wsequence-point` (GCC/Clang) 可捕獲大多數 UB
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved