C ++标准究竟在哪里说取消引用未初始化的指针是未定义的行为?

时间:2010-11-26 14:01:53

标签: c++ standards undefined-behavior language-lawyer

到目前为止,我无法找到如何推断以下内容:

int* ptr;
*ptr = 0;

是未定义的行为。

首先,5.3.1 / 1表明*表示将T*转换为T的间接方式。但这并没有说明UB。

然后经常引用3.7.3.2/4说在非空指针上使用释放函数会使指针无效,以后无效指针的使用是UB。但是在上面的代码中没有关于释放的内容。

如何在上面的代码中推断出UB?

7 个答案:

答案 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范围内的值。由于以这种方式使用甚至无符号整数时可能会导致意外行为,因此没有理由指望指针不会以更糟糕的方式运行。