我正在嘗試學習如何解釋在以下情況下(如果有的話)未定義行為的原因(如下所示)。
int i = 0, *ptr = &i;
i = i; //is this UB? If yes then why according to C 11
*ptr = (*ptr) ; //i think this is UB but i am unable to explain exactly why is this so
*ptr = (*ptr); //i think this is not UB but can't explain why
我已經查看了許多描述 UB 的帖子,這些帖子描述了與上述情況類似的不同指標情況,但我仍然無法解釋為什么確切(比如使用標準中的哪些點,我們可以證明它們會導致 UB)他們導致 UB。
我正在尋找根據 C 11(或 C 14)而不是 C 17 和 Pre-C 11 的解釋。
uj5u.com熱心網友回復:
未定義的行為源于此:
C 11 [intro.execution]/15除非另有說明,否則單個運算子的運算元和單個運算式的子運算式的計算是未排序的......如果標量物件上的副作用相對于另一個副作用是未排序的相同的標量物件或使用相同標量物件的值的值計算,則行為未定義。
C 17 [intro.execution]/17除非另有說明,否則單個運算子的運算元和單個運算式的子運算式的計算是未排序的...如果記憶體位置 (4.4) 上的副作用相對于另一側是未排序的對同一記憶體位置或使用同一記憶體位置中任何物件的值的值計算產生影響,并且它們不是潛在的并發(4.7),行為未定義。
此文類似。主要區別在于“除非特別注明”部分;在 C 17 中,為比在 C 11 中更多的運算子指定了運算元的求值順序。因此:
C 17 [expr.ass]/1在所有情況下,賦值順序在左右運算元的值計算之后,賦值運算式的值計算之前。右運算元在左運算元之前排序。
C 11 缺少粗體部分。這部分i = i 在 C 17 中定義良好,但在 C 11 中未定義。這是因為對于后綴增量,副作用不是運算式的值計算的一部分:
C 11和C 17 [expr.post.incr]/1
運算式的值計算在運算元物件的修改之前排序。
所以“賦值在左右運算元的值計算之后排序”本身是不夠的:賦值在 的值計算之后排序i ,副作用也在相同的值計算之后排序,但沒有說明如何它們是相對于彼此排序的。因此,它們是未排序的,并且它們都在修改同一個物件(此處為i)。這表現出未定義的行為。
在 C 17 中增加“右運算元在左運算元之前排序”意味著 的副作用i 在 的值計算之前排序i,并且兩者都在賦值之前排序。
另一方面,對于預增量,副作用必然是運算式評估的一部分:
C 11 和 C 17 [expr.pre.incr]/1 ... 結果是更新的運算元;它是一個左值...
因此, 的值計算首先 i涉及遞增i,然后應用左值到右值的轉換以獲得更新的值。在 C 11 和 C 17 中,這個值計算是在賦值之前排序的,因此這兩個副作用i是相對于彼此排序的;沒有未定義的行為。
如果i替換為,則此分析中沒有任何變化(*ptr)。這只是參考同一物件或記憶體位置的另一種方式。
uj5u.com熱心網友回復:
C 標準基于 C 標準,其作者不需要任何特定的“理由”來說明實作可以以對其客戶最有用的任何方式處理構造[這就是他們想要的短語“未定義的行為” "的意思]。對于小型原始型別,許多平臺可以廉價地保證涉及對同一物件的讀取和沖突寫入的競爭條件將始終產生舊資料或新資料,并且涉及沖突寫入的競爭條件將導致每個后續讀取看到其中一個書面價值觀。該標準并沒有試圖確定實作應該或不應該支持保證的所有情況,而是允許實作在閑暇時處理代碼“
另請注意,如果要執行以下操作:
*p = (*q) ;
return q[0] q[i]; // where 'i' is some object of type `int`.
當p和q相等且i為零時,編譯器可能會非常合理地生成代碼,其中賦值將撤消增量的影響,但會回傳 q 的舊值加上 1 加上 q 的實際存盤值的總和(這將是舊值,而不是增量值)。盡管這將是指定競爭條件語意的邏輯結果,但試圖精確指定它會非常尷尬,以至于標準只是允許實作按照他們認為合適的方式嚴格或松散地指定行為。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/406182.html
標籤:
