到目前为止,我无法找到如何推断以下内容:
int* ptr;
*ptr = 0;
是未定义的行为。
首先,5.3.1 / 1表明*
表示将T*
转换为T
的间接方式。但这并没有说明UB。
然后经常引用3.7.3.2/4说在非空指针上使用释放函数会使指针无效,以后无效指针的使用是UB。但是在上面的代码中没有关于释放的内容。
如何在上面的代码中推断出UB?
答案 0 :(得分:12)
4.1节看起来像候选人(强调我的):
a的左值(3.10) 非功能,非数组类型T即可 转换为右值。如果T是 不完整的类型,一个程序 需要这种转换是必要的 病态的。如果对象是哪个 左值引用不是类型的对象 T并不是一种类型的对象 派生自T,或如果对象是 未初始化,一个程序 需要进行此转换 未定义的行为。如果T是 非类型,右值的类型 是C的不合格版本的T. 否则,右值的类型是 吨。
我确信在规范中搜索“uninitial”可以找到更多的候选人。
答案 1 :(得分:6)
我发现此问题的答案是C++ draft standard部分24.2
迭代器要求的意外角落,特别是部分24.2.1
段 5 和 10 分别表示(强调我的):
[...] [示例:在声明未初始化指针 x(与int * x;一样)之后,必须始终假定x 具有单数值a指针即可。 -end example] [...] 可解除引用的值总是非单数的。
和
无效的迭代器是一个可能是单数的迭代器。 268
和脚注268
说:
此定义适用于指针,因为指针是迭代器。取消引用已失效的迭代器的效果未定义。
虽然看起来确实存在一些关于whether a null pointer is singular or not的争议,看起来像奇异值一词需要以更一般的方式正确定义。
单数的意图似乎在缺陷报告278. What does iterator validity mean?中总结得很好,理由部分说:
为什么我们说“可能是单数”,而不是“单数”?这是因为有效迭代器是一个已知为非奇异的迭代器。使迭代器失效意味着以一种不再为非奇异的方式对其进行更改。例如:正确地将元素插入向量的中间以使所有指向向量的迭代器无效。 并不一定意味着它们都变得单一。
因此失效并且未初始化 may
创建一个 singular 的值,但由于我们无法证明它们是非奇异我们必须假设它们是单数。
更新
另一种常识性方法是注意标准部分草案5.3.1
一元运算符段落 1 (强调我的 EM>):
一元*运算符执行间接:它所应用的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是指向对象的左值或表达式指向的函数。[...]
然后,如果我们转到3.10
Lvalues和rvalues 段 1 段(强调我的):
左值(历史上所谓的左值,因为左值可能出现在赋值表达式的左侧)指定一个函数或一个对象。 [...]
但ptr
除了偶然之外,不会指向有效的对象。
答案 2 :(得分:5)
OP的问题是无稽之谈。没有要求标准说某些行为是不确定的,事实上我认为所有这些措辞都会从标准中删除,因为它会使人感到困惑并使标准更加冗长而不必要。
标准定义了某些行为。问题是,它是否指定了这种情况下的任何行为?如果没有,则无论是否明确说明,行为都是不确定的。
事实上,有些事情是未定义的规范在标准中主要作为标准编写者的调试辅助,如果在一个地方有一个要求与明确的未定义语句冲突,则产生矛盾的想法另一种行为:这是证明标准缺陷的一种方式。如果没有未明确行为的明确陈述,则规定行为的其他条款将是规范性的且不受质疑。
答案 3 :(得分:3)
我不会假装我对此有很多了解,但是有些编译器会将指针初始化为NULL,并且取消引用指向NULL的指针就是UB。
另外考虑到未初始化的指针可以指向任何东西(这包括NULL),你可以在你取消引用它时得出它是UB。
第8.3.2节[dcl.ref]
中的注释[注意:特别是一个空引用 不能存在于明确的定义中 程序,因为唯一的方法 创造这样的参考将是 将它绑定到由...获得的“对象” 取消引用空指针,其中 导致未定义的行为。如 在9.6中描述,参考不能 直接绑定到位域。 ]
-ISO / IEC 14882:1998(E),ISO C ++标准,见8.3.2节[dcl.ref]
我想我应该把它写成评论而不是,我不太确定。
答案 4 :(得分:3)
要取消引用指针,您需要从指针变量中读取(不要谈论它指向的对象)。从未初始化的变量中读取是未定义的行为。
在阅读之后你对指针的值做了什么,在这一点上不再重要,无论是写入(如在你的例子中)还是从它所指向的对象中读取。
答案 5 :(得分:3)
评估未初始化的指针会导致未定义的行为。由于解除引用指针首先需要对其进行评估,这意味着解除引用也会导致未定义的行为。
虽然措辞有所改变,但在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]中的额外文本。
答案 6 :(得分:1)
即使内存中某些东西的正常存储对于任何陷阱位或陷阱表示没有“空间”,实现也不需要以与静态持续时间变量相同的方式存储自动变量,除非有可能是用户代码可能会在某处保存指向它们的指针。使用整数类型时,此行为最明显。在典型的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);
}
test
产生65540并不特别令人惊讶,即使该值超出uint16_t
的可表示范围,这是一种没有陷阱表示的类型。如果类型uint16_t
的局部变量包含Indeterminate Value,则不要求读取它会产生uint16_t
范围内的值。由于以这种方式使用甚至无符号整数时可能会导致意外行为,因此没有理由指望指针不会以更糟糕的方式运行。