4

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?

Some Name
  • 8,555
  • 5
  • 27
  • 77
  • 1
    Simply - pointer is referencing the object which does not exist anymore. It is the same as returning a pointer to automatic object when exiting the function – 0___________ Sep 06 '21 at 07:01
  • 1
    I wonder if the authors of the Standard considered corner cases involving `goto` when they wrote the lifetime rules for compound literals? The stated rationale for not having their lifetime extend through the function was that doing so would suggest that each iteration of a loop would create a new object, but limiting the lifetime to block scope does nothing to avoid that corner case. As it is, limiting the lifetime to block scope needlessly impairs the usefulness of compound literals, and the specification that only one object is created would forbid loop-unrolling optimizations from... – supercat Sep 07 '21 at 19:25
  • ...having different objects for each iteration of the loop, which could sometimes be very useful with const-qualified literals (e.g. if an inner loop iterates `i` from 0 to 4, and uses a compound literal `(const S){1,2,3,i};` a compiler might usefully generate five static-duration objects, one for each value of `i`, and have each unrolled iteration of the inner loop pass the address of one such object. – supercat Sep 07 '21 at 19:28

1 Answers1

4

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.

Daniel Kleinstein
  • 5,262
  • 1
  • 22
  • 39