((*strDest++=*strSrc++)!='\\0') 為例表達式 ((*strDest++=*strSrc++)!='\\0') 是經典的字串複製片段,常見於 strcpy 實作。它緊湊、高效,卻因涉及後置遞增、指標間接取值、賦值運算子、關係運算子 多重疊加,成為「未定義行為 (UB)」爭論焦點。本文依 C11/C17 標準 (ISO/IEC 9899),以優先序與結合性為刀,層層剖析其確定性執行語義。
| 概念 | 定義 | 本例相關運算子 (高→低) |
|---|---|---|
| 優先序 | 決定「哪個運算子先結合運算元」 | () > ++ (後置) > * (間接) > = > != |
| 結合性 | 同優先序運算子「左結合」或「右結合」 | = 右結合,!= 左結合 |
| 求值順序 | 運算元「實際計算」的先後 (C 標準大多未指定) | 除 && || ?: , 外,運算元求值順序未定義 |
/* 原始 */
((*strDest++ = *strSrc++) != '\0')
/* 依優先序顯式加括號 */
( ( *(strDest++) ) = ( *(strSrc++) ) ) != '\0'
結構拆解:
!= 比較,左運算元為賦值表達式,右運算元為常數 '\0'。= 右結合 → 先求右運算元 *(strSrc++),再求左運算元 *(strDest++),最後執行賦值。*(strSrc++):++ (後置) 優先序高於 * → 相當於 *( (strSrc++) )。*(strDest++):同理。strSrc++:
strSrc 當前值 (指向來源字元)。strSrc 增加 sizeof(*strSrc) (指向下一字元)。)) 之前,順序未指定。* (strSrc 舊值):讀取來源字元值 (如 'A')。strDest++:同理取得目的緩衝區當前位址,strDest 遞增。*dest = src_char:將字元寫入目的緩衝區。賦值表達式的值為寫入後的左運算元值 (即該字元)。!= '\0':比較賦值結果與 0,產生 int 結果 (0 或 1)。strSrc++, strDest++) 確定生效。C11 §6.5p2:「若同一標量物件在兩次序列點之間被修改超過一次,或被修改後又被讀取 (非為了決定要儲存的值),則行為未定義。」
strSrc、strDest 各自只修改一次 (後置 ++)。*strDest 與來源 *strSrc 為不同物件 (假設緩衝區不重疊)。/* 錯誤:同一物件被修改兩次 */
a[i++] = i++; /* UB: i 被修改兩次 */
/* 錯誤:修改後又讀取 (非為決定儲存值) */
x = (x++) + 1; /* UB: x++ 修改 x,外層 + 又讀取 x */
/* 正確:修改後讀取是為了決定儲存值 */
x = x++ + 1; /* 仍 UB! C 標準不認可此理由 */
while (*strSrc != '\0') {
*strDest = *strSrc;
strDest++;
strSrc++;
}
*strDest = '\0';
-Wsequence-point (GCC/Clang) 靜態檢查。((*strDest++=*strSrc++)!='\\0') 在 C 標準下行為完好定義,完成「複製字元、遞增雙指標、檢查結束」三合一功能。其「緊湊」屬於語法糖,非魔法。真正的危險在於誤以為所有緊湊寫法都安全,或將此模式套用至重疊緩衝區 (違反 restrict 語義)。理解優先序、結合性、序列點、副作用四大支柱,才是寫出正確、可維護 C 代碼的根本。