[英]Which of the following combinations of post & pre-increment operators have undefined behaviour in C?
我讀過,有沒有人可以解釋這些未定義的行為(i = i ++ + ++ i,i = i ++等...)並在浪費超過2小時后嘗試理解 “comp.lang.c FAQ”中的序列點時間試圖通過gcc編譯器解釋以下結果。
expression(i=1;j=2) i j k
k = i++ + j++; 2 3 3
k = i++ + ++j; 2 3 4
k = ++i + j++; 2 3 4
k = ++i + ++j; 2 3 5
k = i++ + i++; 3 2
k = i++ + ++i; 3 4
k = ++i + i++; 3 4
k = ++i + ++i; 3 6
i = i++ + j++; 4 3
i = i++ + ++j; 5 3
i = ++i + j++; 4 3
i = ++i + ++j; 5 3
i = i++ + i++; 4
i = i++ + ++i; 5
i = ++i + i++; 5
i = ++i + ++i; 6
題:
我想知道上圖中顯示的所有表達式(在4組中)是否有未定義的行為? 如果只有一些具有未定義的行為,那些行為和哪些不行?
對於已定義的行為表達式,請顯示(不解釋)編譯器如何評估它們。 只是為了確保,如果我正確地獲得了這個預增量和后增量。
背景:
今天,我參加了一次校園訪談,在那里我被要求解釋i++ + ++i
對於給定i
值的結果。 在gcc中編譯該表達式后,我意識到我在采訪中給出的答案是錯誤的。 我決定將來不要犯這樣的錯誤,因此,嘗試編譯前后增量運算符的所有可能組合 ,並在gcc中編譯它們然后嘗試解釋結果。 我掙扎了2個多小時。 我找不到評估這些表達式的單一行為。 所以,我放棄了,轉向stackoverflow。 在閱讀了一些檔案之后,發現有類似sequence point
和未定義的行為。
除第一組外,其他三組中的所有表達式都具有未定義的行為。
如何評估定義的行為(第1組):
i=1, j=2;
k=i++ + j++; // 1 + 2 = 3
k=i++ + ++j; // 1 + 3 = 4
k=++i + ++j; // 2 + 3 = 5
k=++i + j++; // 2 + 2 = 4
這是相當直接的。 后增量與預增量之比。
在第2組和第4組中,很容易看到未定義的行為。
組2具有未定義的行為,因為=
運算符不引入序列點。
境內有這些聲明,沒有序列點。 它們之間有序列點。
如果在連續序列點之間修改同一對象兩次(在這種情況下,通過=
或通過前綴或后綴++
),行為是未定義的。 所以第一組4個語句的行為是明確定義的; 其他人的行為是不明確的。
如果該行為被定義,則i++
產生的先前值i
,和作為副作用修改i
通過加入1
至它。 ++i
通過向其添加1
來修改i
,然后生成修改后的值。
我想知道上圖中顯示的所有表達式(在4組中)是否有未定義的行為?
第2到第5行:
k = i++ + j++;
k = i++ + ++j;
k = ++i + ++j;
k = ++i + j++;
都是明確的。 所有其他表達式都是未定義的,因為它們都試圖通過在序列點之間多次計算表達式來修改對象的值(對於這些示例,序列點出現在';'終止每個語句)。 例如, i = i++;
是未定義的,因為我們試圖通過賦值和后綴++
來修改i
的值而沒有插入序列點。 FYI =
運算符不引入序列點。 ||
&&
?:
和,comma
運營商推出順序點
對於已定義的行為表達式,請顯示(不解釋)編譯器如何評估它們。
讓我們開始吧
k = i++ + j++;
表達a++
計算為的當前值a
,並在下一序列點之前的某個時刻, a
增1。因此, 從邏輯上 ,評價變為像
k = 1 + 2; // i++ evaluates to 1, j++ evaluates to 2
i = i + 1; // i is incremented and becomes 2
j = j + 1; // j is incremented and becomes 3
然而...
評估表達式i++
和j++
的確切順序以及它們的副作用的應用順序是未指定的 。 以下是完全合理的操作順序(使用偽匯編代碼):
mov j, r0 ; read the value of j into register r0
mov i, r1 ; read the value of i into register r1
add r0, r1, r2 ; add the contents of r0 to r1, store result to r2
mov r2, k ; write result to k
inc r1 ; increment value of i
inc r0 ; increment value of j
mov r0, j ; store result of j++
mov r1, i ; store result of i++
不要對算術表達式進行左右評估。 不要認為++
和--
評估后立即更新。
因此,像i++ + ++i
這樣的表達式的結果將根據編譯器,編譯器設置甚至周圍的代碼而變化。 行為未定義,因此編譯器不需要“做正確的事情”,無論可能是什么。 您將得到一個結果,但它不一定是您期望的結果,並且在所有平台上都不一致。
看着
k = i++ + ++j;
邏輯評估是
k = 1 + 3 // i++ evaluates to i (1), ++j evaluates to j + 1 (2 + 1 = 3)
i = i + 1
j = j + 1
同樣,這是一種可能的操作順序:
mov j, r0
inc r0
mov i, r1
add r0, r1, r2
mov r2, k
mov r0, j
inc r1
mov r1, i
或者它可以做其他事情。 編譯器可以自由地改變單個表達式的計算順序,如果它導致更有效的操作順序(我的例子幾乎肯定不是)。
第一組都是定義的。 它們都將i
和j
的值都增加為下一個序列點之前的副作用,所以i
保留為2, j
為3.此外, i++
求值為1, ++i
求值為2, j++
求值到2和++j
計算結果為3.這意味着第一個將1 + 2
分配給k
,第二個將1 + 3
分配給k
,第三個將2 + 3
分配給k
,第四個將2 + 2
分配給k
。
其余的都是未定義的行為。 在第二組和第三組中, i
在序列點之前被修改兩次; 在第四組中, i
在序列點之前被修改三次。
如果編譯器可以告訴兩個左值表達式識別同一個對象,那么讓它以某種合理的方式表現就沒有任何有意義的成本。 更有趣的場景是其中一個或多個操作數是解引用指針的場景。
鑒於代碼:
void test(unsigned *a, unsigned *b, unsigned *c)
{
(*a) = (*b)++ + (*c)++;
}
編譯器可以通過許多合理的方式來處理它。 它可以加載b和c,添加它們,將結果存儲到a,然后遞增b和c,或者它可以加載a和b,計算a + b,a + 1和b + 1,然后將它們寫入任意序列,或執行任何無數其他操作序列。 在某些處理器上,某些安排可能比其他處理器更有效,並且編譯器應該沒有理由期望程序員會認為任何安排比任何其他安排更合適。
請注意,即使在大多數硬件平台上,將相同的指針傳遞給a,b和c也可能會產生有限數量的似是而非的行為,但標准的作者並沒有努力區分似乎合理且難以置信的結果。 盡管許多實現可以很容易地以基本上零成本提供一些行為保證(例如,保證像上面這樣的代碼總是將*a
, *b
和*c
為某些可能未指定的值而沒有任何其他副作用),甚至雖然這樣的保證有時可能是有用的(如果指針在對象的值很重要的情況下識別不同的對象,但可能不會這樣做),編譯器編寫者認為在授予時可以實現有用的優化的可能性很小。全權觸發任意破壞性的副作用將比程序員從受約束行為的保證中獲得的價值更有價值。
逗號有點棘手。 它們在成對時從左到右(對於for循環中的變量)。 如果放置在多個語句中,則不保證以給定順序評估以逗號分隔的語句。 另請注意,如果函數參數和聲明之間用逗號分隔,則無法保證執行的順序。
所以
int a=0;
function_call(++a, ++a, ++a);
會產生不可預測的結果。
在大多數情況下,gcc首先實現預增量並在操作中使用這些值,然后評估后增量。
例如。 在塊2中,預增量為none,因此使用i
1
k = i++ + i++ // hence k = 1+1=2
在i中有兩個后增量,所以i = 3
一個預增量將i更改為2
k = i++ + ++i // hence k= 2+2= 4
i
有一個后增量,所以i= 3
對於k= ++i + i++
i
兩個預增量使其為3
k=++i + ++i // hence k=3+3= 6
i = 3
希望能解釋一下。 但它純粹取決於編譯器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.