繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM