Search code examples
clanguage-lawyerundefined-behaviorcompound-literals

Compound literal is created once for a given scope


I'm pretty confused about N2346::6.5.2.5/15 and N2346::6.5.2.5/16 which states (emp. mine)

15 EXAMPLE 8 Each compound literal creates only a single object in a given scope

struct s { int i; };
int f (void)
{
    struct s *p = 0, *q;
    int j = 0;
    again:
        q = p, p = &((struct s){ j++ });
        if (j < 2) goto again;
    return p == q && q->i == 1;
}

The function f() always returns the value 1.

16 Note that if an iteration statement were used instead of an explicit goto and a labeled statement, the lifetime of the unnamed object would be the body of the loop only, and on entry next time around p would have an indeterminate value, which would result in undefined behavior.

The quote appeared to me as a contradiction to another part of the Standard. Precisely:

N2346::6.5.2.5/5

If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

meaning that block-scoped object created with compound literals have automatic storage duration.

N2346::6.8/3 (emp. mine):

The initializers of objects that have automatic storage duration, and the variable length array declarators of ordinary identifiers with block scope, are evaluated and the values are stored in the objects (including storing an indeterminate value in objects without an initializer) each time the declaration is reached in the order of execution, as if it were a statement, and within each declaration in the order that declarators appear.

So even if the goto statement in the example of N2346::6.5.2.5/15 is replaced with an iteration statement the object created by compound literal should be re-created each time it's reached.

QUESTION: Why does replacing goto with an iteration statement yields UB? What's wrong with my reasoning?


Solution

  • even if the goto statement in the example of N2346::6.5.2.5/15 is replaced with an iteration statement the object created by compound literal should be re-created each time it's reached.

    You are correct - but the important point is that the end of the block signals the end of the object's storage duration. The undefined behavior is triggered on q = p in the second iteration, when p is no longer valid, and also on the return line outside of the iteration statement.


    More concretely, the standard is alluding to code like this:

    struct s { int i; };
    int f (void)
    {
        struct s *p = 0, *q;
        int j = 0;
        for (j = 0; j < 2; j++)
        {
            q = p; // p is invalid in the second iteration
            p = &((struct s){ j++ });
        } // end of block - p is no longer valid!
    
        // p points to an object whose storage duration has expired, and so this is undefined behavior
        return p == q && q->i == 1;
    }
    

    you can see the final return statement referencing an object whose storage duration expired at the end of the for block, and the q variable being assigned to a pointer that's undefined in the second iteration.

    The defining difference between the usage of goto and an iteration statement like a for loop is that objects created inside the for loop are only valid inside the scope of the loop.