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

标签 c++ initialization undefined-behavior language-lawyer

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

int x = x;

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

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

根据 4.1 “左值到右值转换”,对未初始化的值执行左值到右值转换是未定义的行为。做右手x进行这种转换?如果是这样,该示例实际上是否具有未定义的行为?

最佳答案

更新:在评论中的讨论之后,我在这个答案的末尾添加了一些更多的证据。

免责声明 : 我承认这个答案相当投机。另一方面,C++11 标准的当前制定似乎不允许更正式的答案。

的上下文中this Q&A ,已经出现 C++11 标准没有正式指定什么 value categories 是每个语言结构所期望的。在下文中,我将主要关注内置运算符,尽管问题是关于初始化程序。最终,我最终会将我对运算符的情况得出的结论扩展到初始值设定项的情况。

在内置运算符的情况下,尽管缺乏正式规范,但在标准中发现(非规范性)证据表明预期规范是 让 prvalues 在任何需要值的地方都可以预期,如果没有另外指定 .

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

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



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

正如 Johannes Schaub 所指出的,在 4/8 中发现了另一个证据。在链接问答的评论中:

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 段中找到支持这种信念的痕迹,关于引用的初始化(不需要左值初始化表达式的值):

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:

— 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 主要关注“如有必要”这一点,但我也想强调“ 类型 ”这个词。上面的段落提到“如有必要”将使用标准转换来转换为目标类型,但没有说明类别转换:
  • 如果需要,是否会执行类别转换?
  • 他们需要吗?

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

    至于第一个问题,我认为答案也是"is"似乎是合理的。如果它是“否”,显然正确的程序将是格式错误的:
    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 是"is",另一方面,A1 变得至关重要;然而,已经提供了足够的证据来证明它是“是的”。

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

    进一步的证据:

    defect report (由 Jesse Good 提供)提出了一项旨在在这种情况下提供未定义行为的更改:

    [...] 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 段的拟议措辞说:

    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:

    — If T is (possibly cv-qualified) std::nullptr_t, the result is a null pointer constant (4.10 [conv.ptr]).

    — Otherwise, if the glvalue T has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary.

    — Otherwise, if the object to which the glvalue refers contains an invalid pointer value (3.7.4.2 [basic.stc.dynamic.deallocation], 3.7.4.3 [basic.stc.dynamic.safety]), the behavior is implementation-defined.

    — Otherwise, if T is a (possibly cv-qualified) unsigned character type (3.9.1 [basic.fundamental]), and the object to which the glvalue refers contains an indeterminate value (5.3.4 [expr.new], 8.5 [dcl.init], 12.6.2 [class.base.init]), and that object does not have automatic storage duration or the glvalue was the operand of a unary & operator or it was bound to a reference, the result is an unspecified value. [Footnote: The value may be different each time the lvalue-to-rvalue conversion is applied to the object. An unsigned char object with indeterminate value allocated to a register might trap. —end footnote]

    Otherwise, if the object to which the glvalue refers contains an indeterminate value, the behavior is undefined.

    — Otherwise, if the glvalue has (possibly cv-qualified) type std::nullptr_t, the prvalue result is a null pointer constant (4.10 [conv.ptr]). Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.

    关于c++ - 初始化是否需要左值到右值的转换?是 `int x = x;` UB 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14935722/

    相关文章:

    C++:调用派生类的虚函数

    c++ - 这段C++代码有什么问题?

    c - 不能同时初始化 4 个以上的变量吗?

    c++ - C++标准中未定义行为段落中的[Note]是什么意思?

    c++ - 想计算 18 位数字但只会计算到 6 位?使用 long double 但仍然无法正常工作

    c++ - 为什么跳出函数后节点值变了?

    c - 获取未初始化指针的地址是未定义的行为吗?

    generics - [T; 之间有什么区别? N] 和 U 如果 U 总是设置为 [T; N]?

    c++ - 模板成员默认初始化

    arrays - 如何初始化像 std::array 这样的类