GCC warning for uninitialized data seems opposite of what the standard says

85 Views Asked by At

Detailed code is given below, but basically: I have a class with some uninitialized data (not set in the ctor). When copying an instance of this class under certain circumstances, GCC gives no warning, even though it is reading uninitialized data to do so. Then, when wrapping the data in union, GCC does warn, but as far as I understand it, this is the one case where the standard does make a carve-out defining the behavior. So it seems like it should not warn in that second case.

In other words, the GCC behavior seems exactly the opposite of what it should be.

Aside: I realize that this is dealing with undefined behavior, so the compiler doesn't owe me anything, but given that GCC has specific warnings for this scenario, it seems strange that they are in opposition to (my interpretation of) the standard.

Please help me understand the issues at play.

Here is a godbolt live link demonstrating the issue: https://godbolt.org/z/Kdaa8M8qa

Note the use of -Wuninitialized.

1. Simple non-union case

struct MyObj {
    MyObj()
        // leave data uninitialized
        //: val(123), fval(9.5f)
    {}

    // Note: compiler-provided copy constructor
    
    //union {
        int val;
        float fval;
    //};
};

Here, when we put this object in a containing object, and make a copy of an instance of that container, we get no warning from GCC, even though MyObj::val and fval are clearly not initialized, and copying them is thus clearly UB. I would expect -Wuninitialized to catch this obvious issue.

See the use of the Container wrapper class, at the godbolt link given above.

2. Union case

same code, but with the union being used

struct MyObj {

    // ...
    // same as above
    // ...
    
    union {
        int val;
        float fval;
    };
};

Here, we have the same as above, but wrap the members in a union. From my understanding of the standard, unions get special treatment, and they have their "object representation" copied (much like memcpy would do). So, we are allowed to copy uninitialized values in this way, even if they have trap representations, etc.

However, confusingly, with this change, now GCC does issue a warning.

So, I want to know: Is GCC's behavior correct in both cases, and my understanding is wrong? If so, please explain why, in detail.

1

There are 1 best solutions below

3
Artyer On

The error isn't saying the copy assignment is the uninitialized use.

For example, this doesn't have the warning:

cont = Container{ 10, {} };
cont.o.val = 2;  // <-- This is the added line
printf("Internal undefined val: %d (0x%x)\n", cont.o.val, cont.o.val);

The warning is supposed to say <anonymous> (the temporary materialized from Container{ 10, {} }) is being read from when it is not initialized in the line with printf.

Though the error should say something like <temporary>.Container::o.MyObj::<anonymous>.MyObj::<unnamed union>::val and point to the printf line, instead of to the line the temporary was created (maybe it should have both locations?)

It does tell you about the correct line if you use a constructor instead of assigning:

Container cont = Container{ 10, {} };
// cont.o.val = 2;
printf("Internal undefined val: %d (0x%x)\n", cont.o.val, cont.o.val);
<source>:50:11: warning: 'cont.Container::o.MyObj::<anonymous>.MyObj::<unnamed union>::val' is used uninitialized in this function [-Wuninitialized]
     printf("Internal undefined val: %d (0x%x)\n", cont.o.val, cont.o.val);
     ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~