[英]Is dereferencing a pointer that's equal to nullptr undefined behavior by the standard?
[英]Where exactly does C++ standard say dereferencing an uninitialized pointer is undefined behavior?
到目前為止,我找不到如何推斷以下內容:
int* ptr;
*ptr = 0;
是未定義的行為。
首先,有 5.3.1/1 聲明*
表示將T*
轉換為T
的間接。 但這並沒有說明UB。
然后經常引用 3.7.3.2/4 說,在非空指針上使用釋放函數會使指針無效,然后無效指針的使用是 UB。 但在上面的代碼中,沒有關於釋放的內容。
上面的代碼中如何推導出UB?
第4.1節看起來像一個候選人( 重點是 ):
非函數,非數組類型T的左值(3.10)可以轉換為右值。 如果T是不完整的類型,則必須進行這種轉換的程序格式錯誤。 如果左值引用的對象不是類型T的對象,也不是從T派生的類型的對象 ,或者該對象未初始化 ,則需要進行此轉換的程序具有未定義的行為 。 如果T為非類類型,則右值的類型為T的cv不合格版本。否則,右值的類型為T。
我敢肯定,只要在規范中搜索“ uninitial”,就能找到更多候選人。
我發現這個問題的答案是C ++標准草案第24.2
節“ 迭代器要求” (特別是第24.2.1
節)中意料之外的一個方面, 在第5段和第10段中分別說( 強調我的 ):
[...] [示例:在聲明未初始化的指針 x之后(與int * x;一樣), 必須始終假定 x 具有指針的奇異值 。 [-end示例] [...]可引用的值始終不是單數。
和:
無效的迭代器是可能為單數的迭代器。 268
腳注268
說:
該定義適用於指針, 因為指針是迭代器 。 取消引用已經無效的迭代器的效果是不確定的。
盡管看起來確實存在關於空指針是否為奇數的爭議,並且看起來術語“ 奇異值”需要以更一般的方式正確定義。
缺陷報告278中似乎很好地概括了單數 含義。迭代器有效性是什么意思? 在“基本原理”部分下顯示:
為什么我們說“可能是單數”而不是“是單數”? 那是因為有效的迭代器是已知的非奇異迭代器 。 使迭代器無效意味着以一種不再不再是單數的方式更改它。 一個例子:正確地將元素插入向量的中間會使所有指向向量的迭代器無效。 那不一定意味着他們都變得單數 。
因此, 失效和未初始化 may
創建一個奇異的值,但是由於我們無法證明它們是非奇異的 ,因此必須假定它們是奇異的 。
更新資料
另一種常識性方法是要注意,標准草案第5.3.1
節一元運算符第1段( 強調我的 ):
一元*運算符執行間接操作:應用該表達式的表達式應為指向對象類型的指針或為函數類型的指針,並且結果為指向表達式所指向的對象或函數的左值 。 ..]
然后如果轉到第3.10
節“ 左值和右值”,則第1段說( 強調我的意思 ):
左值(之所以稱為歷史值,是因為左值可能出現在賦值表達式的左側)指定函數或對象。 [...]
但是ptr
除非偶然,否則不會指向有效的對象 。
OP的問題是胡說八道。 並沒有要求標准說某些行為是未定義的,確實,我會主張將所有此類措辭從標准中刪除,因為它會使人們感到困惑,並使標准更加冗長。
該標准定義了某些行為。 問題是,在這種情況下,它是否指定了任何行為? 如果不是,則無論行為是否明確表示,行為都是不確定的。
實際上,某些未定義的規范主要留在標准中,作為標准編寫者的調試輔助工具,其思想是,如果某個地方的要求與另一地方的未定義行為的明確陳述相沖突,則會產生矛盾。 :這是證明標准存在缺陷的一種方式。 如果沒有明確聲明未定義的行為,則其他規定行為的條款將是規范性的且不受挑戰的。
評估未初始化的指針會導致未定義的行為。 由於取消引用指針首先需要對其進行評估,因此這意味着取消引用還會導致未定義的行為。
盡管措辭有所變化,但在C ++ 11和C ++ 14中都是如此。
在C ++ 14中,[dcl.init] / 12完全覆蓋了它:
當獲得具有自動或動態存儲持續時間的對象的存儲時,該對象具有不確定的值,並且如果未對該對象執行任何初始化,則該對象將保留不確定的值,直到替換該值為止。
如果評估產生不確定的值,則該行為是不確定的,但以下情況除外:
其中“以下情況”是對unsigned char
特定操作。
在C ++ 11中,[conv.lval / 2]在左值到右值轉換過程(即,從以ptr
表示的存儲區域中檢索指針值)下進行了介紹:
非函數,非數組類型T的glvalue可以轉換為prvalue。 如果T是不完整的類型,則必須進行這種轉換的程序格式錯誤。 如果glvalue引用的對象不是類型T的對象,也不是從T派生的類型的對象 , 或者該對象未初始化,則需要進行此轉換的程序將具有未定義的行為。
對於C ++ 14,刪除了粗體部分,並用[dcl.init / 12]中的多余文本代替。
我不會假裝對此了解很多,但是某些編譯器會初始化指向NULL的指針,而取消引用指向NULL的指針就是UB。
同樣考慮到未初始化的指針可能指向任何東西(包括NULL),您可以在取消引用它時得出它是UB的結論。
第8.3.2節[dcl.ref]中的注釋
[注:尤其是,空引用不能存在於定義良好的程序中,因為創建此類引用的唯一方法是將其綁定到通過解引用空指針而獲得的“對象” ,這會導致未定義的行為 。 如9.6中所述,引用不能直接綁定到位域。 ]
— ISO / IEC 14882:1998(E),ISO C ++標准,第8.3.2節[dcl.ref]
我想我應該把它寫為評論,但我不確定。
要取消引用指針,您需要從指針變量中讀取(不要談論它指向的對象)。 從未初始化的變量讀取是未定義的行為。
讀取指針后,對指針的值執行的操作現在不再重要,無論是寫入(如您的示例中)還是從其指向的對象讀取。
即使在內存中正常存儲某些內容也不留任何陷阱位或陷阱表示的“空間”,也不需要實現以與靜態持續時間變量相同的方式存儲自動變量,除非有可能用戶代碼可能保留指向他們某個地方的指針。 此行為在整數類型中最明顯。 在典型的32位系統上,給出以下代碼:
uint16_t foo(void);
uint16_t bar(void);
uint16_t blah(uint32_t q)
{
uint16_t a;
if (q & 1) a=foo();
if (q & 2) a=bar();
return a;
}
unsigned short test(void)
{
return blah(65540);
}
即使該值超出uint16_t
的可表示范圍(沒有陷阱表示形式的類型),對於test
產生65540的test
也就不足為奇了。 如果類型為uint16_t
的局部變量具有不確定的值,則不要求讀取它會產生uint16_t
范圍內的值。 由於以這種方式使用甚至無符號整數也可能導致意外行為,因此沒有理由指望指針不會以更差的方式表現。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.