This is a theoretical question, I know how to do this unambiguously, but I got curious and dug into the standard and I need a second pair of standards lawyer eyes.
Let's start with two structs and one init function:
struct foo {
int a;
};
struct bar {
struct foo *f;
};
struct bar *
init_bar(struct foo *f)
{
struct bar *b = malloc(sizeof *b);
if (!b)
return NULL;
b->f = f;
return b;
}
We now have a sloppy programmer who doesn't check return values:
void
x(void)
{
struct bar *b;
b = init_bar(&((struct foo){ .a = 42 }));
b->f->a++;
free(b);
}
From my reading of the standard there's nothing wrong here other than potentially dereferencing a NULL pointer. Modifying struct foo
through the pointer in struct bar
should be legal because the lifetime of the compound literal sent into init_bar
is the block where it's contained, which is the whole function x
.
But now we have a more careful programmer:
void
y(void)
{
struct bar *b;
if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
err(1, "couldn't allocate b");
b->f->a++;
free(b);
}
Code does the same thing, right? So it should work too. But more careful reading of the C11 standard is leading me to believe that this leads to undefined behavior. (emphasis in quotes mine)
6.5.2.5 Compound literals
5 The value of the compound literal is that of an unnamed object initialized by the initializer list. 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.
6.8.4 Selection statements
3 A selection statement is a block whose scope is a strict subset of the scope of its enclosing block. Each associated substatement is also a block whose scope is a strict subset of the scope of the selection statement.
Am I reading this right? Does the fact that the if
is a block mean that the lifetime of the compound literal is just the if statement?
(In case anyone wonders about where this contrived example came from, in real code init_bar
is actually pthread_create
and the thread is joined before the function returns, but I didn't want to muddy the waters by involving threads).
The second part of the Standard you quoted (6.8.4 Selection statements) says this. In code:
{//scope 1
if( ... )//scope 2
{
}//end scope 2
}//end scope 1
Scope 2 is entirely inside scope 1. Note that a selection statement in this case is the entire if statement, not just the brackets:
if( ... ){ ... }
Anything defined in that statement is in scope 2. Therefore, as shown in your third example, the lifetime of the compound literal, which is declared in scope 2, ends at the closing if bracket (end scope 2), so that example will cause undefined behavior if the function returns non-NULL (or NULL if err() doesn't terminate the program).
(Note that I used brackets in the if statement, even though the third example doesn't use them. That part of the example is equivalent to this (6.8.2 Compound statement):
if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
{
err(1, "couldn't allocate b");
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With