初始化是否需要进行左值到右值转换?是`int x = x;`UB? [英] Does initialization entail lvalue-to-rvalue conversion? Is `int x = x;` UB?

查看:143
本文介绍了初始化是否需要进行左值到右值转换?是`int x = x;`UB?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C ++标准在3.3.2声明点中包含了一个半知名的名称查找示例:

  int x = x; 

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



这是否是未定义的行为?



根据4.1Lvalue-to-rvalue转换,对未初始化的值执行Lvalue-to-Rvalue转换是未定义的行为。右侧 x 是否进行此转换?

更新: 按照以下步骤执行操作:a。如果是,则该示例实际上是否具有未定义的行为?

解决方案






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






这个问题& A ,可以看出C ++ 11标准未能正式指定 价值类别 是每种语言结构所期望的。在下文中,我将主要关注内置运算符,虽然问题是关于 initializers 。最终,我将最终将我对运算符的结果扩展到初始化器的情况。



在内置运算符的情况下,尽管缺少正式规范,(非规范)证据在标准中发现,预期的规范是在需要值的任何地方预期pr值, strong>。



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


第5节中对每个内置操作符的讨论表明它产生的值的类别和它期望的操作数的值类别。例如,内置赋值运算符期望左操作数是一个左值,右操作数是一个prvalue,并产生一个左值作为结果。用户定义的运算符是函数,类别的值他们的期望值和产量由他们的参数和返回类型确定


另一方面,赋值操作符的第5.17节提到这一点。但是,在注释(第5.17 / 1段)中提到了执行左值到右值转换的可能性:


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


当然,如果没有期望值,这个注释将是无意义的。



另一个证据在4/8中找到,如 Johannes Schaub 在链接Q& A的评论中:


在某些情况下,某些转化被禁止。例如,对于一元和二元的操作数不进行左值到右值的转换。运算符。


这似乎暗示了左值到右值的转换是在所有操作数的内置操作符,除非另有规定。这意味着,除非另有说明,否则右值将作为内置运算符的操作数。






CONJECTURE:



即使初始化不是赋值,因此操作员不会进入讨论,我怀疑这个面积受到上述完全相同的问题的影响。



即使在第8.5.2 / 5节中,关于引用的初始化不需要lvalue初始化器表达式):


通常左值到左值指针(4.2)和函数到指针(4.3)的标准转换不需要,因此当这样的直接绑定到左值时,被抑制。


通常一词似乎意味着在初始化不是引用类型的对象时,将要应用左值到右值的转换。



因此,我相信,尽管对初始化器的期望值类别的要求是不明确的(如果不是完全缺失的),但基于证据,假设假设 em>规范是:



如果语言构造需要某个值,除非另有规定,否则预期为prvalue。



在这个假设下,在你的例子中需要一个左值到右值的转换,这将导致未定义的行为。



< hr>

其他证据



为了提供进一步的证据来支持这个推测,它错误,因此,确实需要复制初始化的左值到右值转换,并考虑以下代码(感谢 jogojapan ):

  int y; 
int x = y; // No UB
short t;
int u = t; // UB! (不喜欢这种不一致,但可以接受它)
int z;
z = x; // No UB(x is not uninitialized)
z = y; // UB! (假设赋值运算符期望值为prvalue,参见上面)
//这将是非常违反直觉的,因为x == y

这种非均匀的行为对我来说没有什么意义。更有意义的是,IMO是在任何需要价值的地方,预期价值。



此外,因为 Jesse Good 在他的答案中正确地指出了C ++标准的关键段落是8.5 / 16:


- 否则,正在初始化的对象的初始值是初始化表达式的
(可能转换的)值。将使用标准
转换(第4条),(如有必要)
初始值设定器表达式转换为
目标 c类型的cv非限定版本
;不考虑用户定义的转换。如果不能进行
转换,则初始化是错误的。 [注意:
类型为cv1 T的表达式可以独立于cv限定符cv1和cv2初始化类型为cv2 T
的对象。


然而,虽然Jesse主要关注如果必要的话,我也想强调字词 type 。上述段落提到,如果必要,将使用标准转化转换为目标类型,但不会说明类别

  • 是否需要类别转换?

  • 关于第二个问题,如答案的原始部分所讨论的,C ++ 11标准目前没有规定类别转换需要或不需要,因为没有提到复制初始化期望作为初始化器的prvalue。因此,一个明确的答案是不可能的。但是,我相信我提供了足够的证据来假设这是规定,因此答案将是是。



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

      int y = 0; 
    int x = y; // y是lvalue,prvalue expected(假设猜想是正确的)

    =对问题1的回答,A2 =对问题2的回答):



    < | A2 =是| A2 = No |
    --------- | ------------ | ---------
    A1 = Yes | UB |没有UB |
    A1 = No |不成体|没有UB |
    ---------------------------------

    如果A2是否,A1不重要:没有UB,但是第一个例子的奇怪的情况(例如 z = y 给出UB,但不是 z = x ,即使 x == y )show向上。如果A2是是,另一方面,A1变得至关重要;



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






    证据:



    缺陷报告 (由 Jesse Good )在这种情况下提出了旨在给予未定义行为的改变:


    <此外,4.1 [conv.lval]段落1说,将左值 - 右值转换应用于未初始化的对象会导致未定义的行为; 这应该以具有不确定值的对象重新表达。


    对于第4.1段说:


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



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



    - 否则,如果glvalue T有一个类类型,转换复制初始化一个临时类型



    - 否则,如果glvalue引用的对象包含无效的指针值(3.7) .4.2 [basic.stc.dynamic.deallocation],3.7.4.3 [basic.stc.dynamic.safety]),该行为是实现定义的。



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



    - 否则,如果glvalue引用的对象包含一个不确定的值,则该行为是未定义的。 >

    - 否则,如果glvalue具有(可能是cv限定的)类型std :: nullptr_t,则prvalue结果是一个空指针常量(4.10 [conv.ptr])。否则,由glvalue指示的对象中包含的值是prvalue结果。



    The C++ standard contains a semi-famous example of "surprising" name lookup in 3.3.2, "Point of declaration":

    int x = x;
    

    This initializes x with itself, which (being a primitive type) is uninitialized and thus has an indeterminate value (assuming it is an automatic variable).

    Is this actually undefined behaviour?

    According to 4.1 "Lvalue-to-rvalue conversion", it is undefined behaviour to perform lvalue-to-rvalue conversion on an uninitialized value. Does the right-hand x undergo this conversion? If so, would the example actually have undefined behaviour?

    解决方案

    UPDATE: Following the discussion in the comments, I added some more evidence at the end of this answer.


    Disclaimer: I admit this answer is rather speculative. The current formulation of the C++11 Standard, on the other hand, does not seem to allow for a more formal answer.


    In the context of this Q&A, it has emerged that the C++11 Standard fails to formally specify what value categories are expected by each language construct. In the following I will mostly focus on built-in operators, although the question is about initializers. Eventually, I will end up extending the conclusions I drew for the case of operators to the case of initializers.

    In the case of built-in operators, in spite of the lack of a formal specification, (non-normative) evidences are found in the Standard that the intended specification is to let prvalues be expected wherever a value is needed, and when not specified otherwise.

    For instance, a note in Paragraph 3.10/1 says:

    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

    Section 5.17 on assignment operators, on the other hand, does not mention this. However, the possibility of performing an lvalue-to-rvalue conversion is mentioned, again in a note (Paragraph 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

    Of course, if no rvalue were expected, this note would be meaningless.

    Another evidence is found in 4/8, as pointed out by Johannes Schaub in the comments to linked Q&A:

    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.

    This seems to imply that lvalue-to-rvalue conversion is performed on all operands of built-in operators, except when specified otherwise. This would mean, in turn, that rvalues are expected as operands of built-in operators unless specified otherwise.


    CONJECTURE:

    Even though initialization is not assignment, and therefore operators do not enter the discussion, my suspicion is that this area of the specification is affected by the very same problem described above.

    Traces supporting this belief can be found even in Paragraph 8.5.2/5, about the initialization of references (for which the value of the lvalue initializer expression is not needed):

    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.

    The word "usual" seems to imply that when initializing objects which are not of a reference type, lvalue-to-rvalue conversion is meant to apply.

    Therefore, I believe that although requirements on the expected value category of initializers are ill-specified (if not completely missing), on the grounds of the evidences provided it makes sense to assume that the intended specification is that:

    Wherever a value is required by a language construct, a prvalue is expected unless specified otherwise.

    Under this assumption, an lvalue-to-rvalue conversion would be required in your example, and that would lead to Undefined Behavior.


    ADDITIONAL EVIDENCE:

    Just to provide further evidence to support this conjecture, let's assume it wrong, so that no lvalue-to-rvalue conversion is indeed required for copy-initialization, and consider the following code (thanks to jogojapan for contributing):

    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
    

    This non-uniform behavior does not make a lot of sense to me. What makes more sense IMO is that wherever a value is required, a prvalue is expected.

    Moreover, as Jesse Good correctly points out in his answer, the key Paragraph of the C++ Standard is 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.

    However, while Jesse mainly focuses on the "if necessary" bit, I would also like to stress the word "type". The paragraph above mentions that standard conversions will be used "if necessary" to convert to the destination type, but does not say anything about category conversions:

    1. Will category conversions be performed if needed?
    2. Are they needed?

    For what concerns the second question, as discussed in the original part of the answer, the C++11 Standard currently does not specify whether category conversions are needed or not, because nowhere it is mentioned whether copy-initialization expects a prvalue as an initializer. Thus, a clear-cut answer is impossible to give. However, I believe I provided enough evidence to assume this to be the intended specification, so that the answer would be "Yes".

    As for the first question, it seems reasonable to me that the answer is "Yes" as well. If it were "No", obviously correct programs would be ill-formed:

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

    To sum it up (A1 = "Answer to question 1", A2 = "Answer to question 2"):

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

    If A2 is "No", A1 does not matter: there's no UB, but the bizarre situations of the first example (e.g. z = y giving UB, but not z = x even though x == y) show up. If A2 is "Yes", on the other hand, A1 becomes crucial; yet, enough evidence has been given to prove it would be "Yes".

    Therefore, my thesis is that A1 = "Yes" and A2 = "Yes", and we should have Undefined Behavior.


    FURTHER EVIDENCE:

    This defect report (courtesy of Jesse Good) proposes a change that is aimed at giving Undefined Behavior in this case:

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

    In particular, the proposed wording for Paragraph 4.1 says:

    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.

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

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆