初始化是否需要左值到右值的转换?是`int x = x;` UB 吗?

C++ 标准在 3.3.2声明点"中包含一个半著名的令人惊讶"名称查找示例:

int x = x;

这用自身初始化 x,它(作为原始类型)未初始化,因此具有不确定的值(假设它是一个自动变量).

这实际上是未定义的行为吗?

根据4.1左值到右值转换",对未初始化的值执行左值到右值转换是未定义的行为.右手边的 x 是否进行了这种转换?如果是这样,该示例实际上是否会有未定义的行为?

gongjie7623 回答:初始化是否需要左值到右值的转换?是`int x = x;` UB 吗?

更新: 根据评论中的讨论,我在此答案的末尾添加了更多证据.

免责声明:我承认这个答案是推测性的.另一方面,C++11 标准的当前表述似乎不允许提供更正式的答案.

这个问答,发现C++11标准没有正式指定什么值类别 是每种语言结构所期望的.下面我将主要关注内置操作符,尽管问题是关于初始化器.最终,我会将我从运算符的情况得出的结论扩展到初始化程序的情况.

在内置运算符的情况下,尽管缺乏正式的规范,但在标准中发现了(非规范性的)证据,预期规范是让在需要值的任何地方以及未另行指定时,均应使用纯右值.

例如,第 3.10/1 段中的注释说:

第 5 条中对每个内置运算符的讨论指出了它产生的值的类别以及它期望的操作数的值类别.例如,内置赋值运算符期望左操作数是左值,右操作数是右值并产生左值作为结果. 用户定义的运算符是函数,类别它们期望和产生的值由它们的参数和返回类型决定

The discussion of each built-in operator in Clause 5 indicates the category of the value it yields and the value categories of the operands it expects. For example, the built-in assignment operators expect that the left operand is an lvalue and that the right operand is a prvalue and yield an lvalue as the result. User-defined operators are functions, and the categories of values they expect and yield are determined by their parameter and return types

另一方面,关于赋值运算符的第 5.17 节没有提到这一点.但是,在注释(第 5.17/1 段)中再次提到了执行左值到右值转换的可能性:

因此,函数调用不应干预左值到右值的转换和与任何单个复合赋值运算符相关的副作用

Therefore, a function call shall not intervene between the lvalue-to-rvalue conversion and the side effect associated with any single compound assignment operator

当然,如果不期望右值,则此注释将毫无意义.

另一个证据是在 4/8 中发现的,正如 Johannes Schaub 在对链接问答:

在某些情况下,某些转换会被抑制.例如,不在一元 & 的操作数上进行左值到右值的转换.操作员.在这些运算符和上下文的描述中给出了特定的例外情况.

There are some contexts where certain conversions are suppressed. For example, the lvalue-to-rvalue conversion is not done on the operand of the unary & operator. Specific exceptions are given in the descriptions of those operators and contexts.

这似乎意味着对内置运算符的所有操作数都执行左值到右值的转换,除非另有说明.这意味着,除非另有说明,否则右值应作为内置运算符的操作数.

推测:

尽管初始化不是赋值,因此操作符没有进入讨论,但我怀疑规范的这一领域受到上述相同问题的影响.

甚至可以在第 8.5.2/5 段中找到支持这种信念的痕迹,关于引用的初始化(对于它来说不需要左值初始化表达式的值):

不需要通常左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 的标准转换,因此被抑制,当这种对左值的直接绑定完成时.

The usual lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done.

通常"这个词似乎暗示在初始化不是引用类型的对象时,应该应用左值到右值的转换.

因此,我认为虽然对初始化器的期望值类别的要求没有明确规定(如果不是完全缺失),但根据证据,假设预期规范是:

在语言结构需要值的地方,除非另有说明,否则预期为纯右值.

在此假设下,您的示例中需要进行左值到右值的转换,这将导致未定义行为.

其他证据:

只是为了提供进一步的证据来支持这个猜想,让我们假设它错误,这样复制初始化确实不需要左值到右值的转换,并考虑以下代码(感谢jogojapan 贡献):

int y;
int x = y; // No UB
short t;
int u = t; // UB! (Do not like this non-uniformity, but could accept it)
int z;
z = x; // No UB (x is not uninitialized)
z = y; // UB! (Assuming assignment operators expect a prvalue, see above)
       // This would be very counterintuitive, since x == y

这种不统一的行为对我来说没有多大意义.IMO 更有意义的是,无论何处需要值,都需要一个纯右值.

此外,正如 Jesse Good 在他的回答中正确指出的那样,C++ 标准的关键段落是 8.5/16:

——否则,被初始化的对象的初始值是(可能已转换)初始化表达式的值.标准如有必要,将使用转换(第 4 条)来转换初始化表达式到 cv 非限定版本的目的地类型;不考虑用户定义的转换.如果无法进行转换,初始化格式错误.[ 笔记:cv1 T"类型的表达式可以初始化cv2 T"类型的对象独立于 cv 限定符 cv1 和 cv2.

— Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions (Clause 4) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. [ Note: An expression of type "cv1 T" can initialize an object of type "cv2 T" independently of the cv-qualifiers cv1 and cv2.

然而,虽然 Jesse 主要关注if必要"这一点,但我也想强调类型"这个词.上面的段落提到了if必要"将使用标准转换来转换为目标类型,但没有说明类别转换:

  1. 是否会在需要时执行类别转换?
  2. 需要它们吗?

关于第二个问题,正如答案的原始部分所讨论的,C++11标准目前没有指定是否需要类别转换,因为没有提到复制初始化是否需要纯右值作为初始化程序.因此,不可能给出明确的答案.但是,我相信我提供了足够的证据来假设这是预期的规范,因此答案是是".

至于第一个问题,我认为答案也是是"似乎是合理的.如果它是否",显然正确的程序将是格式错误的:

int y = 0;
int x = y; // y is lvalue, prvalue expected (assuming the conjecture is correct)

总结起来(A1 =问题1的答案",A2 =问题2的答案"):

          | A2 = Yes   | A2 = No |
 ---------|------------|---------|
 A1 = Yes |     UB     |  No UB  | 
 A1 = No  | ill-formed |  No UB  |
 ---------------------------------

如果 A2 为否",则 A1 无关紧要:没有 UB,但是第一个示例的奇怪情况(例如 z = y 给出 UB,但不是 z = x 即使 x == y) 出现.如果 A2 是是",另一方面,A1 变得至关重要;然而,已经提供了足够的证据来证明它会是".

因此,我的论点是 A1 = "Yes" 和 A2 = "Yes",我们应该有未定义行为.

进一步的证据:

这个缺陷报告(由 Jesse Good 提供a>) 提出了一个旨在在这种情况下提供未定义行为的更改:

[...] 此外,4.1 [conv.lval] 第 1 段说将左值到右值转换应用于未初始化的对象"会导致未定义的行为;这应该改写为具有不确定值的对象.

[...] In addition, 4.1 [conv.lval] paragraph 1 says that applying the lvalue-to-rvalue conversion to an "object [that] is uninitialized" results in undefined behavior; this should be rephrased in terms of an object with an indeterminate value.

特别是,第 4.1 段的拟议措辞说:

当在未计算的操作数或其子表达式(第 5 条 [expr])中发生左值到右值的转换时,引用对象中包含的值不会被访问.在所有其他情况下,转换结果根据以下规则确定:

When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof (Clause 5 [expr]) the value contained in the referenced object is not accessed. In all other cases, the result of the conversion is determined according to the following rules:

——如果 T 是(可能有 cv 限定的)std::nullptr_t,结果是一个空指针常量(4.10 [conv.ptr]).

——否则,如果泛左值 T 具有类类型,则转换会从泛左值复制初始化 T 类型的临时值,并且转换的结果是临时值的纯右值.

——否则,如果泛左值引用的对象包含无效的指针值(3.7.4.2 [basic.stc.dynamic.deallocation], 3.7.4.3 [basic.stc.dynamic.safety]),则行为为实现定义.

——否则,如果 T 是(可能是 cv 限定的)无符号字符类型(3.9.1 [basic.fundamental]),并且泛左值所指的对象包含不确定值(5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]),并且该对象没有自动存储持续时间或者泛左值是一元 & 的操作数.运算符或者它被绑定到一个引用,结果是一个未指定的值.[脚注:每次将左值到右值转换应用于对象时,该值可能会有所不同.分配给寄存器的具有不确定值的无符号字符对象可能会陷入陷阱.——结束脚注]

否则,如果泛左值引用的对象包含不确定值,则行为未定义.

——否则,如果泛左值具有(可能有 cv 限定的)类型 std::nullptr_t,则纯右值结果是空指针常量 (4.10 [conv.ptr]).否则,泛左值所指示的对象中包含的值就是纯右值结果.

这篇关于初始化是否需要左值到右值的转换?是`int x = x;` UB 吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持前端之家!

本文链接:https://www.f2er.com/3188534.html

大家都在问