14

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

int* ptr;
*ptr = 0;

是未定义的行为。

首先,有 5.3.1/1 声明这*意味着间接转换T*T. 但这并没有说明UB。

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

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

4

7 回答 7

13

第 4.1 节看起来像一个候选人(强调我的):

非函数、非数组类型 T 的左值 (3.10) 可以转换为右值。如果 T 是不完整类型,则需要这种转换的程序是非良构的。如果左值引用的对象不是 T 类型的对象,也不是从 T 派生的类型的对象,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为。如果 T 是非类类型,则右值的类型是 T 的 cv 非限定版本。否则,右值的类型是 T。

我敢肯定,在规范中搜索“非初始”可以找到更多候选人。

于 2010-11-26T14:18:39.060 回答
6

我发现这个问题的答案是C++ 草案标准的一个意想不到的角落,24.2 迭代器要求部分,特别是24.2.1 一般510段, 它们分别说(强调我的):

[...][ 示例:在声明未初始化的指针x 之后(与 int* x; 一样),必须始终假定 x 具有指针的奇异值。—end example ] [...]可取消引用的值总是非单数的。

和:

无效的迭代器是可能是单数的迭代器。268

脚注268说:

这个定义适用于指针,因为指针是迭代器。取消引用已失效的迭代器的效果是未定义的。

尽管看起来对于空指针是否为单数存在一些争议,并且看起来需要以更一般的方式正确定义术语单数。

单数的意图似乎在缺陷报告278 中得到了很好的总结。迭代器有效性是什么意思?在理由部分说:

为什么我们说“可能是单数”,而不是“单数”?这是因为一个有效的迭代器是一个已知为nonsingular 的迭代器。使迭代器无效意味着以不再知道它是非奇异的方式对其进行更改。一个例子:将一个元素插入到向量的中间被正确地称为使指向向量的所有迭代器无效。这并不一定意味着它们都变成了单数

所以失效未初始化创建了一个 奇异may的值,但由于我们无法证明它们是非奇异的,我们必须假设它们是奇异的。

更新

另一种常识方法是注意标准草案5.3.1 一元运算符1段,其中说(强调我的):

一元 * 运算符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是一个左值,指向表达式指向的对象或函数。 [。 ..]

如果我们然后转到3.10 Lvalues 和 rvalues部分,第1段说(强调我的):

左值(历史上称为左值,因为左值可能出现在赋值表达式的左侧)指定函数或对象。[...]

ptr除非偶然,否则不会指向有效对象

于 2013-12-16T15:10:49.513 回答
5

OP的问题是胡说八道。没有要求标准说某些行为是未定义的,实际上我认为所有这些措辞都应从标准中删除,因为它会使人们感到困惑并使标准变得不必要地冗长。

该标准定义了某些行为。问题是,它在这种情况下是否指定了任何行为?如果没有,无论它是否明确表示,行为都是未定义的。

事实上,有些事情未定义的规范主要作为标准编写者的调试辅助工具留在标准中,其想法是如果一个地方的需求与另一个地方的未定义行为的明确声明相冲突,则产生矛盾:这是证明标准中存在缺陷的一种方式。如果没有对未定义行为的明确声明,规定行为的其他条款将是规范性的并且不会受到挑战。

于 2010-12-13T18:01:50.773 回答
4

评估未初始化的指针会导致未定义的行为。由于取消引用指针首先需要评估它,这意味着取消引用也会导致未定义的行为。

这在 C++11 和 C++14 中都是如此,尽管措辞发生了变化。

在 C++14 中,它被 [dcl.init]/12 完全覆盖:

当获得具有自动或动态存储持续时间的对象的存储时,该对象具有一个不确定的值,如果没有对该对象执行初始化,则该对象保留一个不确定的值,直到该值被替换。

如果评估产生不确定的值,则行为未定义,但以下情况除外:

其中“以下情况”是对unsigned char.


在 C++11 中,[conv.lval/2] 在左值到右值的转换过程(即从由 表示的存储区域中检索指针值)中涵盖了这一点ptr

非函数、非数组类型 T 的左值可以转换为纯右值。如果 T 是不完整类型,则需要这种转换的程序是非良构的。如果泛左值所引用的对象不是 T 类型的对象,也不是从 T 派生的类型的对象,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为。

C++14 的粗体部分已被删除,并替换为 [dcl.init/12] 中的额外文本。

于 2015-06-08T01:11:51.837 回答
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]

我想我应该把它写成评论,我不太确定。

于 2010-11-26T14:13:52.423 回答
3

要取消引用指针,您需要从指针变量中读取(而不是谈论它指向的对象)。从未初始化的变量中读取是未定义的行为。

读取指针的值后,您对指针的值做什么,此时不再重要,无论是写入(如您的示例中)还是从它指向的对象中读取。

于 2010-11-26T14:41:28.553 回答
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. 由于以这种方式使用无符号整数时可能会导致意外行为,因此没有理由期望指针不会以更糟糕的方式表现。

于 2015-06-08T16:31:40.827 回答