Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this undefined behaviour or a bug with struct init?

Please consider this bit of code:

#include <iostream>

int main()
{
    struct A 
    { 
        int x; 
        int y; 
        int z; 

        int foo()
        {
            std::cout << "enter foo: " << this->x << "," << this->y << "," << this->z << std::endl;
            return 5;
        }       

        int moo() 
        { 
            std::cout << "enter moo: " << this->x << "," << this->y << "," << this->z << std::endl;
            this->x = 1;
            this->z = 10;
            return 2; 
        }
    };

    A b { b.foo(), b.z = b.moo(), 3};

    std::cout << "final: " << b.x << "," << b.y << "," << b.z << std::endl;

    return 0;
}

The result in my VS2017 (x64 release):

enter foo: 0,0,0
enter moo: 5,0,0
final: 1,2,3

The result from ideone.com (gcc 6.3) https://ideone.com/OGqvjW):

enter foo: 0,0,3
enter moo: 5,0,3
final: 1,2,2

One compiler sets z member to 3 immediately, before everything, then overwrites it when methods and assignments are called, another does it at the very end, after everything.

Q. What would be the explanation to such behaviour?

Thank you.

like image 989
Killzone Kid Avatar asked Mar 08 '23 06:03

Killzone Kid


1 Answers

Yes, this is undefined behavior:

int foo()
{
    std::cout << "enter foo: " << this->x << "," << this->y << "," << this->z << std::endl;
    //                            ~~~~~~~           ~~~~~~~           ~~~~~~~
}       

At the point that foo() is invoked, x, y, and z have not been initialized yet. From [dcl.init]/12:

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 ([expr.ass]). [...] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases: [...]

None of the remaining cases apply. So printing x, y, and z there is undefined behavior. No different from just:

int x;
std::cout << x; // ub

My previous answer said yes, but for lifetime reasons. It had suggested that the initialization in A b{ b.foo(), b.z = b.moo(), 3}; is non-vacuous and hence any access of any of the members of b before the end of initialization is UB. However, xskxzr has since indicated to me that in order for initialization to be non-vacuous, you must have constructors invoked, and int does not have constructors. This makes the initialization of b vacuous. Which seems conceptually odd to me, but the wording in this regard is clear.

like image 52
Barry Avatar answered Mar 15 '23 08:03

Barry