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).