現代 C (C11/C17/C23)

先序於關係(sequenced-before relation)

/ SEE-kwenst /

運算式的某一部分何時發生在另一部分之前?在單一執行緒中,你或許以為程式碼嚴格由上而下執行,但在「單一運算式內」,C 給編譯器重排與重疊子運算的自由。先序於關係是「哪些求值有先後、哪些無序、邊界在哪」的精確規則——它決定了像 i = i++ 這樣的程式碼是否有良好定義。

較舊的 C 使用「序列點(sequence point)」:在某些點(分號處整個完整運算式的結尾、逗號運算子、&&、||、?:、以及函式呼叫前對其引數的求值)之前,所有先前的副作用都必須完成。C11 用三種求值 A 與 B 之間的關係重新表述:先序於表示 A 完全排在 B 之前;若兩者都不先序於對方,它們就是「未定序(unsequenced)」(實作可任意順序執行或交錯它們);而「不確定定序(indeterminately sequenced)」表示一個完全發生在另一個之前,但你不知道是哪一個(這就是不同函式呼叫之間的關係)。會咬人的規則是:若對同一物件的兩個副作用、或一個副作用與對它的讀取是未定序的,行為就是未定義的。

它重要在於它是 as-if 規則與「何謂可觀測行為」的基礎:只要保留先序於關係所看得見的結果,編譯器可以重排任何東西。經典陷阱正是那些未定序的情況——a = a++、arr[i] = i++、或 g 與 h 彼此未定序的 f(g(), h())——這些你都不該寫。

int i = 1; i = i++; // 未定義:對 i 的兩個副作用未定序 int a[3], j = 0; a[j] = j++; // 未定義:寫 a[j] 與更新 j 未定序 // 改成有良好定義:i++; 或 a[j] = 0; j++;

對 i 的兩個未定序修改(或對 j 的寫入與更新)是未定義行為,而非僅僅「不可預測」。

對同一物件的未定序副作用是未定義行為——編譯器可假設它從不發生並據此最佳化,所以結果不是「某種順序」,而是根本沒有任何已定義的意義。

又称
sequencingsequence points (older model)定序序列點