簡體   English   中英

非取消引用的迭代器是否超過了數組未定義行為的“一個過去的”迭代器?

[英]Are non dereferenced iterators past the "one past-the-end" iterator of an array undefined behavior?

給定int foo[] = {0, 1, 2, 3}; 我想知道指向“過去的最后一個”的迭代器是否無效。 例如: auto bar = cend(foo) + 1;

在這樣的堆棧溢出問題中有大量的抱怨和警告說這是“未定義的行為”: c++ what's the result of iterator + integer when past-end-iterator? 不幸的是,唯一的來源是揮手。

我在購買它時遇到了越來越多的麻煩,例如:

int* bar;

未初始化,但肯定不會調用未定義的行為,並且經過足夠的嘗試,我確信我可以找到一個實例,其中未初始化bar中的值與cend(foo) + 1具有相同的值。

這里最大的困惑之一是我不是在詢問取消引用cend(foo) + 1 我知道那將是未定義的行為,標准禁止這樣做。 但是像這樣的答案: https://stackoverflow.com/a/33675281/2642059僅引用取消引用這樣的迭代器是非法的回答問題。

我也知道 C++ 只保證 cend cend(foo)有效,但它可能是numeric_limits<int*>::max() ,在這種情況下cend(foo) + 1會溢出。 我對這種情況不感興趣,除非它在標准中被稱為我們不能讓迭代器超過“一個過去的結束”的原因。 我知道int*實際上只包含一個 integer 值,因此會溢出。

我想要一個可靠來源的引文,即將迭代器移動到“一個過去的”元素之外是未定義的行為。

是的,如果您形成這樣的指針,則程序具有未定義的行為。

這是因為您唯一的方法是將有效指針增加到它指向其內部的對象的邊界之外,這是未定義的操作。

[C++14: 5.7/5]: 將具有整數類型的表達式添加到指針或從指針中減去時 ,結果具有指針操作數的類型。 如果指針操作數指向數組對象的元素,並且數組足夠大 ,則結果指向與原始元素偏移的元素,以使結果數組元素和原始數組元素的下標之差等於整數表達式。 換句話說,如果表達式P指向數組對象的第i個元素,則表達式(P)+N等效為N+(P) )和(P)-N (其中N的值為n )指向數組對象的第i + n個元素和第i − n個元素(如果存在)。 此外,如果表達式P指向數組對象的最后一個元素,則表達式(P)+1指向數組對象的最后一個元素之后,如果表達式Q指向數組對象的最后一個元素之后,表達式(Q)-1指向數組對象的最后一個元素。 如果指針操作數和結果都指向同一數組對象的元素,或者指向數組對象的最后一個元素,則求值不會產生溢出; 否則,行為是不確定的。

未初始化的指針不是一回事,因為除了聲明它(顯然是有效的)外,您從沒有做任何事情來“獲取”該指針。 但是,如果不對程序賦予未定義的行為,您甚至無法對其進行評估(不是取消引用- 評估 )。 直到您為它分配了一個有效值。

附帶說明一下,我不會將這些稱為“過去的”迭代器/指針,這是C ++中的一個術語,具體表示“ 一個過去的”迭代器/指針是有效的(例如cend(foo)本身) )。 你是waaaay過去的結束。 ;)

TL; DR-由於迭代過程中違反了先決條件,因此計算經過一次過去的迭代器的迭代器是未定義的行為。


亮度提供了權威地涵蓋指針的引用。

對於迭代器,通常不禁止遞增到“結束”(最后一個元素),但是對於大多數各種迭代器,禁止這樣做:

迭代器要求

InputIterator要求

輸入迭代器要求,尤其是唯一的可遞增的(如果可取消引用的子句)要求通過引用合並到前向,雙向和隨機訪問迭代器中。

輸出迭代器不受此限制,它們始終是可遞增的。 由於沒有終點,因此過去定義的迭代器將被排除在外,因此擔心它們是否合法才值得擔心。

然后,根據單個增量定義序列中的前跳,因此我們得出結論,對於所有迭代器類型,過去一過去的迭代器的計算要么沒有意義,要么是非法的

RandomAccessIterator要求

我對這種情況不感興趣,除非在標准中將其作為標准,因為我們不能讓迭代器超越“一個過去的一端”。 我知道int *實際上只保存一個整數值,因此容易溢出。

該標准沒有討論使事情變得不確定的原因。 您的邏輯倒數了:它是未定義的事實是實現可能將對象放置在否則可能導致溢出的位置的原因。 如果要求“兩端通過”迭代器是有效的,則要求實現將對象放在可能導致此類操作溢出的位置。

正如@ Random842所說的那樣:

該標准並未將指針類型描述為處於具有最小和最大值的平坦線性空間中,並且介於兩者之間的所有值都是有效的,因為您似乎認為它們是

假定指針不存在於平坦的線性空間中。 而是有有效的指針和無效的指針。 對指針的某些操作是已定義的,其他操作是未定義的行為。

在許多現代系統上,指針是在平坦的線性空間中實現的。 即使在這些系統上,形成一些指針的不確定性也可以使C ++編譯器進行一些優化。 例如, int foo[5]; bool test(int* it1) { int* it2 = cend(foo); return it1 <= it2; } int foo[5]; bool test(int* it1) { int* it2 = cend(foo); return it1 <= it2; } int foo[5]; bool test(int* it1) { int* it2 = cend(foo); return it1 <= it2; }可以優化為true因為沒有可以與it2有效比較的指針不少於或等於它。

在較不人為的情況下(例如某些循環),這可以節省每個循環的周期。

考慮到這一點,不太可能開發出指針模型。 有一些指針實現不在平坦的線性空間中。

分段內存是最著名的。 在舊的x86系統中,每個指針都是一對16位值。 它們在線性20位地址空間中引用的位置為high << 4 + low ,或segment << 4 + offset

對象位於一個段中,並且具有恆定的段值。 這意味着所有定義的指針<比較都可以簡單地比較 16位低的offset 他們不必做那個數學運算(這在當時是很昂貴的),他們可以丟棄高16位並在訂購時比較偏移值。

還有其他一些體系結構,其中代碼存在於數據的並行地址空間中(因此將代碼指針與數據指針進行比較可以返回虛假的相等性)。

規則很簡單。 可以創建指向數組中元素的指針,以及指向最后一句的指針(這意味着分段內存系統無法構建到達該段末尾的數組)。

現在,您的內存沒有被分段,所以這不是您的問題,對嗎? 編譯器可以自由地沿着某個代碼分支解釋ptr+2形成,以表示ptr 不是指向數組最后一個元素的指針,並可以進行相應的優化。 如果不是這樣,您的代碼可能會以意外的方式運行。

如果不是這種情況,那么就有一些使用類似技術的真實編譯器實例(假設代碼不使用未定義的行為,從中證明不變量,使用結論在未定義的行為發生之前更改行為)。 未定義的行為可能會花費時間,即使底層的硬件實現“不會有任何問題”而沒有任何優化。

暫無
暫無

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

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