Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compound literal lifetime and if blocks

Tags:

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

like image 271
Art Avatar asked Jan 19 '16 15:01

Art


1 Answers

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");
}
like image 85
2501 Avatar answered Oct 17 '22 09:10

2501