簡體   English   中英

C++ 中賦值語句的求值順序

[英]Order of evaluation of assignment statement in C++

map<int, int> mp;
printf("%d ", mp.size());
mp[10]=mp.size();
printf("%d\n", mp[10]);

這段代碼產生的答案不是很直觀:

0 1

我理解它為什么會發生 - 賦值的左側返回對mp[10]基礎值的引用,同時創建上述值,然后才使用新計算的size()評估右側map。

C++ 標准中的任何地方都說明了這種行為嗎? 還是評估順序未定義?

使用 g++ 5.2.1 獲得結果。

是的,這是標准涵蓋的,它是未指定的行為。 最近的 C++ 標准提案中涵蓋了這種特殊情況: N4228: Refining Expression Evaluation Order for Idiomatic C++旨在細化評估規則的順序,使其在某些情況下得到很好的指定。

它描述了這個問題如下:

表達式求值順序是 C++ 社區中反復出現的討論主題。 簡而言之,給定一個表達式,例如f(a, b, c) ,標准未指定子表達式f, a, b, c的計算順序。 如果這些子表達式中的任何兩個碰巧修改了同一個對象而沒有干預序列點,則程序的行為是未定義的。 例如,表達式f(i++, i)其中 i 是一個整數變量會導致未定義行為, v[i] = i++ 也是如此 即使行為不是未定義的,評估表達式的結果仍然可以是任何人的猜測。 考慮以下程序片段:

 #include <map> int main() { std::map<int, int> m; m[0] = m.size(); // #1 }

在評估標記為 #1 的語句后,地圖對象 m 應該是什么樣子? { {0, 0 } } 或 {{0, 1 } } ?

我們知道,除非指定子表達式的計算是無序的,否則來自C++11 標准草案1.9程序執行,其中說:

除非另有說明,否則對單個運算符的操作數和單個表達式的子表達式的評估是無序的。[...]

並且所有第5.17節賦值和復合賦值運算符 [expr.ass] 說的是:

[...]在所有情況下,賦值順序在左右操作數的值計算之后,賦值表達式的值計算之前。[...]

所以本節沒有確定評估的順序,但我們知道這不是未定義的行為,因為operator []size()都是函數調用,第1.9節告訴我們(強調我的):

[...] 調用函數時(無論函數是否內聯),與任何參數表達式或指定被調用函數的后綴表達式相關的每個值計算和副作用,都在執行每個表達式或語句之前進行排序在被調用函數的主體中。 [ 注意:與不同參數表達式相關的值計算和副作用是無序的。 —end note ]調用函數(包括其他函數調用)中的每個求值在被調用函數的主體執行之前或之后沒有特別排序,相對於被調用函數的執行是不確定的.9[. ..]

請注意,我在“C++ 編程語言”第 4 版第 36.3.6 節中的這段代碼是否具有明確定義的行為的問題中介紹了N4228提案中的第二個有趣示例 .

更新

上次 WG21 會議上,進化工作組似乎接受了N4228的修訂版,但論文 ( P0145R0 ) 尚不可用。 所以這在 C++17 中可能不再是未指定的。

更新 2

的修訂3 p0145作出這個指定而更新[expr.ass] P1

賦值運算符 (=) 和復合賦值運算符都從右到左分組。 所有都需要一個可修改的左值作為它們的左操作數; 他們的結果是一個引用左操作數的左值。 如果左操作數是位域,則所有情況下的結果都是位域。 在所有情況下,賦值順序都在左右操作數的值計算之后,賦值表達式的值計算之前。 右操作數排在左操作數之前。 ...

從 C++11 標准(強調我的):

5.17 賦值和復合賦值運算符

1 賦值運算符 (=) 和復合賦值運算符都從右到左分組。 所有都需要一個可修改的左值作為它們的左操作數,並返回一個引用左操作數的左值。 如果左操作數是位域,則所有情況下的結果都是位域。 在所有情況下,賦值順序在左右操作數的值計算之后,賦值表達式的值計算之前。

語言沒有指定是先計算左操作數還是先計算右操作數。 編譯器可以自由選擇首先評估任一操作數。 由於您的代碼的最終結果取決於操作數的計算順序,我會說這是未指定的行為而不是未定義的行為。

1.3.25 未指明的行為

行為,對於格式良好的程序構造和正確的數據,這取決於實現

我確定標准沒有指定表達式x = y; 在 C++ 標准中計算xy順序(例如,這就是為什么不能執行*p++ = *p++的原因,因為p++不是按定義的順序完成的)。

換句話說,要保證x = y;順序x = y; 按照定義的順序,您需要將其分解為兩個序列點。

 T tmp = y;
 x = tmp;

(當然,在這種特殊情況下,人們可能會假設編譯器更喜歡在size()之前執行operator[]因為它可以將值直接存儲到operator[]的結果中,而不是將其保存在臨時位置,以存儲它稍后在operator[]被評估之后 - 但我很確定編譯器不需要按那個順序做)

讓我們來看看您的代碼分解為:

mp.operator[](10).operator=(mp.size());

這幾乎說明了在第一部分中創建了 10 的條目,在第二部分中,容器的大小被分配給位置 10 的整數引用。

但是現在您進入了未指定的評估順序問題。 這是一個簡單得多的例子

何時應該調用map::size() ,在map::operator(int const &);之前或之后map::operator(int const &); ?

沒有人真正知道。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM