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

複雜 C 表達式執行步驟深度剖析:以 ((*strDest++=*strSrc++)!='\\0') 為例

     編輯:C 語言基礎

引言:一行代碼引發的標準之爭

表達式 ((*strDest++=*strSrc++)!='\\0') 是經典的字串複製片段,常見於 strcpy 實作。它緊湊、高效,卻因涉及後置遞增、指標間接取值、賦值運算子、關係運算子 多重疊加,成為「未定義行為 (UB)」爭論焦點。本文依 C11/C17 標準 (ISO/IEC 9899),以優先序與結合性為刀,層層剖析其確定性執行語義。

關鍵概念:優先序 vs 結合性 vs 求值順序

概念定義本例相關運算子 (高→低)
優先序決定「哪個運算子先結合運算元」() > ++ (後置) > * (間接) > = > !=
結合性同優先序運算子「左結合」或「右結合」= 右結合,!= 左結合
求值順序運算元「實際計算」的先後 (C 標準大多未指定)除 && || ?: , 外,運算元求值順序未定義

語法樹建構:依優先序加括號

/* 原始 */
((*strDest++ = *strSrc++) != '\0')

/* 依優先序顯式加括號 */
( ( *(strDest++) ) = ( *(strSrc++) ) ) != '\0'

結構拆解:

  1. 最外層:!= 比較,左運算元為賦值表達式,右運算元為常數 '\0'。
  2. 賦值表達式:= 右結合 → 先求右運算元 *(strSrc++),再求左運算元 *(strDest++),最後執行賦值。
  3. 右運算元 *(strSrc++):++ (後置) 優先序高於 * → 相當於 *( (strSrc++) )。
  4. 左運算元 *(strDest++):同理。

逐步語義推導 (C11 §6.5.2.4, §6.5.16, §6.5.9)

  1. 求值 strSrc++:
    • 運算元值 = strSrc 當前值 (指向來源字元)。
    • 副作用:strSrc 增加 sizeof(*strSrc) (指向下一字元)。
    • 副作用完成時機:下一序列點 (此處為完整表達式結尾 )) 之前,順序未指定。
  2. 執行 * (strSrc 舊值):讀取來源字元值 (如 'A')。
  3. 求值 strDest++:同理取得目的緩衝區當前位址,strDest 遞增。
  4. 執行賦值 *dest = src_char:將字元寫入目的緩衝區。賦值表達式的值為寫入後的左運算元值 (即該字元)。
  5. 執行 != '\0':比較賦值結果與 0,產生 int 結果 (0 或 1)。
  6. 序列點到達:所有副作用 (strSrc++, strDest++) 確定生效。

為何此表達式無未定義行為?

C11 §6.5p2:「若同一標量物件在兩次序列點之間被修改超過一次,或被修改後又被讀取 (非為了決定要儲存的值),則行為未定義。」

  • strSrc、strDest 各自只修改一次 (後置 ++)。
  • 修改後的值未在表達式中再次讀取 (讀取的是修改前的值)。
  • 賦值目標 *strDest 與來源 *strSrc 為不同物件 (假設緩衝區不重疊)。
  • ∴ 完全符合標準,行為確定。

對比:真正的 UB 陷阱

/* 錯誤:同一物件被修改兩次 */
a[i++] = i++;           /* UB: i 被修改兩次 */

/* 錯誤:修改後又讀取 (非為決定儲存值) */
x = (x++) + 1;          /* UB: x++ 修改 x,外層 + 又讀取 x */

/* 正確:修改後讀取是為了決定儲存值 */
x = x++ + 1;            /* 仍 UB! C 標準不認可此理由 */

最佳實務建議

  1. 教學/面試:能完整推導上述語義,展現對標準深度理解。
  2. 生產代碼:拆分為多行,配合註解:
    while (*strSrc != '\0') {
        *strDest = *strSrc;
        strDest++;
        strSrc++;
    }
    *strDest = '\0';
    
  3. 程式碼規範:MISRA C 2012 Rule 13.2 禁止在同一表達式中對同一物件多次副作用;建議啟用 -Wsequence-point (GCC/Clang) 靜態檢查。

結論

((*strDest++=*strSrc++)!='\\0') 在 C 標準下行為完好定義,完成「複製字元、遞增雙指標、檢查結束」三合一功能。其「緊湊」屬於語法糖,非魔法。真正的危險在於誤以為所有緊湊寫法都安全,或將此模式套用至重疊緩衝區 (違反 restrict 語義)。理解優先序、結合性、序列點、副作用四大支柱,才是寫出正確、可維護 C 代碼的根本。

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