Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is modifying a variable in its declaration statement well-defined?

For example:

#include<iostream>
using namespace std;
int main() {
    int i = i=0; //no warning
    cout << i << endl;
    return 0;
}

Compiled in vs2015 with no warning and output 0. Is this code snippet well-defined although it seems a little weird?

However, in this online compiler (g++ prog.cc -Wall -Wextra -std=c++17) it throws a warning of:

prog.cc: In function '`int main()`':  
prog.cc:8:12: warning: operation on '`i`' may be undefined [-Wsequence-point]
     `int i=i=0;`
like image 371
scottxiao Avatar asked Apr 19 '18 02:04

scottxiao


2 Answers

There are two possible cases, depending on whether the object's lifetime has begun. This is determined by the first rule in [basic.life]:

The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. [ Note: Initialization by a trivial copy/move constructor is non-vacuous initialization. — end note ] The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • if the object has non-vacuous initialization, its initialization is complete, except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union, or as described in ([class.union]).
  1. Objects of class or aggregate type

    std::string s = std::to_string(s.size()); // UB
    

    In this case, the lifetime of the object doesn't start until initialization is complete, so this rule in [basic.life] is applicable:

    Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see ([class.cdtor]). Otherwise, such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

    • the glvalue is used to access the object, or
    • the glvalue is used to call a non-static member function of the object, or
    • the glvalue is bound to a reference to a virtual base class, or
    • the glvalue is used as the operand of a dynamic_cast or as the operand of typeid.

    In this example, the glvalue is used to access a non-static member, resulting in undefined behavior.

  2. Objects of primitive type

    int i = (i=0); // ok
    int k = (k&0); // UB
    

    Here, even though there is an initializer, the initialization cannot be non-vacuous because of the type. Therefore, the object's lifetime has started and the above rule does not apply.

    Still, the existing value in the object is indeterminate (unless the object has static storage duration, in which case static initialization gives it a value of zero). A glvalue referring to an object with indeterminate value must never undergo lvalue-to-rvalue conversion. Thus "write-only" operations are permitted, but most1 operations reading the indeterminate value lead to undefined behavior.

    The applicable rule is found in [dcl.init]:

    If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced. [ Note: Objects with static or thread storage duration are zero-initialized, see ([basic.start.static]). — end note ]

    If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

    • If an indeterminate value of unsigned narrow character type or std::byte type is produced by the evaluation of:

      • the second or third operand of a conditional expression,
      • the right operand of a comma expression,
      • the operand of a cast or conversion to an unsigned narrow character type or std::byte type, or
      • a discarded-value expression,

      then the result of the operation is an indeterminate value.

    • If an indeterminate value of unsigned narrow character type or std::byte type is produced by the evaluation of the right operand of a simple assignment operator whose first operand is an lvalue of unsigned narrow character type or std::byte type, an indeterminate value replaces the value of the object referred to by the left operand.
    • If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when initializing an object of unsigned narrow character type, that object is initialized to an indeterminate value.
    • If an indeterminate value of unsigned narrow character type or std::byte type is produced by the evaluation of the initialization expression when initializing an object of std::byte type, that object is initialized to an indeterminate value.

1 There's a narrow exception for using character types to copy indeterminate values, making the destination value also indeterminate. The value still can't be used in other operations such as bitwise operators or arithmetic.

like image 191
Ben Voigt Avatar answered Nov 15 '22 15:11

Ben Voigt


Is modifying variable in its declaration statement well-defined?

int i = i=0;//no warning

The statement above is initialization and is well-defined since the two i's are in the same scope.

As per basic.scope.pdecl#1

The point of declaration for a name is immediately after its complete declarator and before its initializer (if any), except as noted below. [ Example:

unsigned char x = 12; // Warning -Wunused-variable
{ unsigned char x = x; }
                ^ warning -Wuninitialized

Here the second x is initialized with its own (indeterminate) value. — end example ]

In the example, the second x is in a different block scope, its value is then indeterminate. And will have a warning:

warning: 'x' is used uninitialized in this function [-Wuninitialized]

Given the fact that local variables with automatic storage will have indeterminate value if not initialized, I believe there's an assignment taking place which has this order.

int (i = (i = 0));

Example

int x; // indeterminate
int i = i = x = 2; // x is assigned to two, then x's value is assigned to i
cout << x << " " << i; // i = 2, x = 2
like image 33
Joseph D. Avatar answered Nov 15 '22 16:11

Joseph D.