19

TL;DR

Given the following code:

int* ptr;
*ptr = 0;

does *ptr require an lvalue-to-rvalue conversion of ptr before applying indirection?

The standard covers the topic of lvalue-to-rvalue in many places but does not seem to specify enough information to determine whether the * operator require such a conversion.

Details

The lvalue-to-rvalue conversion is covered in N3485 in section 4.1 Lvalue-to-rvalue conversion paragraph 1 and says (emphasis mine going forward):

A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue.53 If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.[...]

So does *ptr = 0; necessitate this conversion?

If we go to section 4 paragraph 1 it says:

[...]A standard conversion sequence will be applied to an expression if necessary to convert it to a required destination type.

So when is it necessary? If we look at section 5 Expressions the lvalue-to-rvalue conversion is mentioned in paragraph 9 which says:

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue. [...]

and paragraph 11 which says:

In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression.[...] The lvalue-to-rvalue conversion (4.1) is applied if and only if the expression is an lvalue of volatile-qualified type and it is one of the following [...]

neither paragraph seems to apply to this code sample and 5.3.1 Unary operators paragraph 1 it says:

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. If the type of the expression is “pointer to T,” the type of the result is “T.” [ Note: indirection through a pointer to an incomplete type (other than cv void) is valid. The lvalue thus obtained can be used in limited ways (to initialize a reference, for example); this lvalue must not be converted to a prvalue, see 4.1. —end note ]

it does not seem to require the value of the pointer and I don't see any requirements for a conversion of the pointer here am I missing something?

Why do we care?

I have seen an answer and comments in other questions that claim the use of an uninitialized pointer is undefined behavior due the need for an lvalue-to-rvalue conversion of ptr before applying indirection. For example: Where exactly does C++ standard say dereferencing an uninitialized pointer is undefined behavior? makes this argument and I can not reconcile the argument with what is laid out in any of the recent draft versions of the standard. Since I have seen this several times I wanted to get clarification.

The actual proof of undefined behavior is not as important since as I noted in the linked question above we have others way to get to undefined behavior.

4

2 回答 2

13

我认为你是从一个相当倾斜的角度来解决这个问题的,可以这么说。根据§5.3.1/1:

一元运算*符执行间接:应用它的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是一个左值,指向表达式指向的对象或函数。如果表达式的类型是“指向 T 的指针”,则结果的类型是“T”。</p>

虽然这没有讨论左值到右值的转换,但它要求表达式是指向对象或函数的指针。未初始化的指针不会(除非可能是偶然的)是任何这样的事情,因此取消引用的尝试会产生未定义的行为。

于 2014-01-10T20:25:14.067 回答
4

我已将问题中的更新部分转换为答案,因为此时它似乎是答案,尽管我的问题无法回答令人不满意:

dyp向我指出了两个相关主题,它们涵盖了非常相似的领域:

共识似乎是标准不明确,因此无法提供我正在寻找的答案,约瑟夫曼斯菲尔德 发布了一份关于缺乏规范的缺陷报告,看起来它仍然是开放的,不清楚何时可以澄清一下。

关于标准的意图,有一些常识性的论据。从逻辑上讲,如果操作需要使用该操作数的值,则操作数是纯右值。另一个论点是,如果我们回顾C99 草案标准,默认情况下会进行左值到右值的转换,并且会注明例外情况。C99 标准草案的相关部分是6.3.2.1 左值、数组和函数指示符2段,其中说:

除非它是 sizeof 运算符、一元 & 运算符、++ 运算符、-- 运算符或 . 的左操作数的操作数。运算符或赋值运算符,不具有数组类型的左值将转换为存储在指定对象中的值(并且不再是左值)。[…]

这基本上说除了一些例外,操作数被转换为存储的值,并且由于间接不是一个例外,如果这被澄清在C++中也是如此,那么它确实可以回答我的问题yes

当我试图澄清未定义行为的证明时,不如澄清是否强制要求左值到右值转换重要。如果我们想证明未定义的行为,我们有替代方法。Jerry 的方法是一种常识,因为间接要求表达式是指向对象或函数的指针,并且不确定的值只会偶然指向有效对象。一般来说,C++ 标准草案没有明确声明使用不确定值是未定义的,这与 C99 标准草案不同。例外是迭代器和扩展指针,我们确实有奇异值的概念我们在部分中被告知24.2.1

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

和:

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

脚注 268 说:

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

在 C++1y中,语言发生了变化,我们确实有一个明确的声明,使用未定义的中间值,但有一些狭窄的例外

于 2014-01-19T05:58:03.983 回答