C ++标准究竟如何解引用未初始化的指针是未定义的行为?
到目前为止,我无法find如下推论:
int* ptr; *ptr = 0;
是未定义的行为。
首先,5.3.1 / 1表示*
表示将T*
转换为T*
间接方式。 但是这并没有提到UB。
然后经常引用3.7.3.2/4,指出对一个非空指针使用释放函数会使指针无效,而后来无效指针的使用是UB。 但是在上面的代码中,没有关于释放的内容。
如何在上面的代码中推导UB?
4.1节看起来像一个候选人( 重点是我的 ):
非函数非数组typesT的左值(3.10)可以转换为右值。 如果T是不完整的types,那么需要这种转换的程序是不合格的。 如果左值引用的对象不是Ttypes的对象,并且不是T派生types的对象 ,或者对象未初始化 ,则需要此转换的程序具有未定义的行为 。 如果T是一个非types的types,则右值的types是T的cv不合格版本。否则,右值的types是T.
我相信只要在规范中寻找“非初始”就可以find更多的候选人。
我发现这个问题的答案是C ++草案标准的一个意想不到的angular落,第24.2
节迭代器的要求 ,具体是第24.2.1
节, 在第5和第10节中分别说明( 强调我的 ):
[例子:在声明了一个未初始化的指针 x之后(和int * x;一样), 必须假设 x 必须有一个指针的奇异值 。 – 例子] […]可引用的值总是非单一的。
和:
无效的迭代器可能是单数的迭代器。 268
脚注268
说:
这个定义适用于指针, 因为指针是迭代器 。 取消引用已经失效的迭代器的效果是未定义的。
尽pipe对于空指针是单数还是不存在争议似乎存在一些争议,看起来像单数值这个术语需要以更一般的方式进行适当的定义。
单数的意图似乎在缺陷报告278中得到了很好的总结。迭代器的有效性是什么意思? 根据理由部分说:
为什么我们说“可能是单数” ,而不是“单数”呢? 这是因为有效的迭代器是已知的非奇异的迭代器 。 使迭代器无效意味着改变它,使其不再被称为非奇怪的。 一个例子:将一个元素插入到一个向量的中间,正确地说,使所有指向该向量的迭代器无效。 这并不一定意味着它们都变得单一 。
因此, 失效和未初始化 may
创造一个奇异的价值,但由于我们不能certificate它们是非奇异的,我们必须假设它们是单数的 。
更新
另一个常识方法是注意标准部分5.3.1
一元运算符第1段说( 重点是我的 ):
一元运算符执行间接性:应用它的expression式应该是一个指向对象types的指针,或者指向函数types的指针, 结果是一个左值,指向expression式所指向的对象或函数。 ..]
如果我们再去第3.10
节左值和右值第1段说( 重点是我的 ):
一个左值(所谓的,在历史上,因为左值可以出现在赋值expression式的左侧)指定一个函数或一个对象。 […]
但ptr
不会,除非偶然,指向一个有效的对象 。
OP的问题是无稽之谈。 “标准”没有要求某些行为是不确定的,事实上,我认为所有这些措辞都是从标准中删除的,因为这会混淆人们,使标准更为冗长。
该标准定义了某些行为。 问题是,它在这种情况下是否指定了任何行为? 如果不这样做,行为是不确定的,不pipe它是否如此明确地表示。
事实上,有些东西是未定义的规范留在标准中,主要作为标准编写者的debugging帮助,如果在一个地方有一个需求与另一个未定义行为的明确声明相冲突,那么这个想法就会产生一个矛盾:这是certificate标准缺陷的一种方法。 如果没有明确表述不明确的行为,另一个规定行为的规则将是规范的和不受质疑的。
我不会假装对此知道很多,但是有些编译器会将指针初始化为NULL,而取消引用NULL的指针是UB。
另外考虑到未初始化的指针可以指向任何东西(这包括NULL),你可以得出结论,当你解引用时,它是UB。
第8.3.2节[dcl.ref]
[注意:特别是空引用不能存在于定义良好的程序中,因为创build这种引用的唯一方法是将其与通过解引用空指针而获得的“对象”绑定,这会导致未定义的行为 。 如9.6所述,引用不能直接绑定到位域。 ]
–ISO / IEC 14882:1998(E),ISO C ++标准,在第8.3.2节[dcl.ref]
我想我应该写这个评论,而不是我确定的。
要取消引用指针,需要从指针variables中读取数据(而不是指向它所指向的对象)。 从未初始化的variables读取是未定义的行为。
在读完指针之后,如何处理指针的值,在这一点上是无关紧要的,无论是写入(就像在你的例子中),还是从指向的对象读取。
评估未初始化的指针会导致未定义的行为。 由于解引用指针首先需要评估它,这意味着解引用也会导致未定义的行为。
在C ++ 11和C ++ 14中都是如此,尽pipe措辞改变了。
在C ++ 14中,它完全由[dcl.init] / 12覆盖:
当获得具有自动或dynamic存储持续时间的对象的存储时,该对象具有不确定的值,并且如果没有对该对象执行初始化,该对象将保留不确定的值,直到该值被replace。
如果评估产生了不确定的值,除非在以下情况下行为是不确定的:
“以下情况”是对unsigned char
特定操作。
在C ++ 11中,[conv.lval / 2]在左值到右值转换过程(即从ptr
表示的存储区域中检索指针值)下覆盖了这一点:
一个非函数的非数组typesT的glvalue可以转换为一个prvalue。 如果T是不完整的types,那么需要这种转换的程序是不合格的。 如果glvalue引用的对象不是Ttypes的对象,也不是T派生types的对象 , 或者对象未初始化,则需要此转换的程序具有未定义的行为。
粗体部分被删除了C ++ 14,并用[dcl.init / 12]中的额外文本replace。
即使在内存中的东西的正常存储将没有任何陷阱位或陷阱表示的“空间”,实现不需要像静态持续variables那样存储自动variables,除非用户代码可能存在一个指向他们的地方。 这种行为对于整数types是最明显的。 在典型的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
的可表示范围之外(即没有陷阱表示的types), test
也不会特别令人惊讶。 如果uint16_t
types的局部variables包含Indeterminate Value,则不要求读取它就会产生uint16_t
范围内的值。 由于以这种方式使用即使是无符号整数也会导致意想不到的行为,因此没有理由期望指针不能以更糟糕的方式行事。