What is the lifetime of compound literals passed as arguments?
Asked Answered
A

2

11

This compiles without warnings using clang.

typedef struct {
  int option;
  int value;
} someType;

someType *init(someType *ptr) {
  *ptr = (someType) {
    .option = ptr->option | ANOTHEROPT,
    .value = 1
  };

  return ptr;
}

int main()
{
  someType *typePtr = init( &(someType) {
    .option = SOMEOPT
  });
  // do something else with typePtr
}
  1. Is this even valid C?

  2. If so: What is the lifetime of the compound literal?

Antipodes answered 19/2, 2014 at 13:53 Comment(2)
If you knew the name of what you were using did you attempt to research the feature. These two excellent pieces come up 1 and 2 for me: The New C: Compound Literals and 6.25 Compound Literals and they both answer your question.Eraser
I have read those (and several more), but didn't find this specific example anywhere.Antipodes
D
10

It's valid C in C99 or above.

C99 §6.5.2.5 Compound literals

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.

In your example, the compound literal has automatic storage, which means, its lifetime is within its block, i.e, the main() function that it's in.

Recommended reading from @Shafik Yaghmour:

  1. The New C: Compound Literals
  2. GCC Manual: 6.25 Compound Literals
Dervish answered 19/2, 2014 at 13:57 Comment(0)
B
4

Yu Hao has answered with the standard, now some vulgarization.

Whenever you see a compound literal like:

struct S *s;
s = &(struct S){1};

you can replace it with:

struct S *s;
struct S __HIDDEN_NAME__ = {1};
s = &__HIDDEN_NAME__;

So:

main.c

#include <assert.h>

struct S {int i;};
/* static: lives for the entire program. */
struct S *s1 = &(struct S){1};
struct S *s2;
struct S *s3;
struct S *s4;

int f(struct S *s) {
    return s->i + 1;
}

int main() {
    /* Undefined behaviour: not defined yet.
     * GCC 10 -fsanitize=undefined -ggdb3 -O0 -std=c99 gives at runtime:
     * runtime error: member access within null pointer of type 'struct S' */
#if 0
    assert(f(s2) == 1);
#endif

    /* Lives inside main, and any function called from main. */
    s2 = &(struct S){1};
    /* Fine because now instantiated. */
    assert(f(s2) == 2);

    /* Only lives in this block. */
    {
        s3 = &(struct S){1};
        /* Fine. */
        assert(f(s3) == 2);
    }
    {
        /* On GCC 10 -O0, this replaces s3 above due to UB */
        s4 = &(struct S){2};
    }
    /* Undefined Behavior: lifetime has ended in previous block.
     * On GCC 10, ubsan does not notice it, and the assert fails
     * due to the s4 overwrite.*/
#if 0
    assert(s3->i == 1);
#endif
}

Full compilation command:

gcc -fsanitize=undefined -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
Business answered 2/7, 2015 at 8:8 Comment(3)
If you ever update the example, for clarity please rename the variables to s1 (global), s2 (main) and s3 (sub-block). The last comment doesn't seem right to me as I think you mean to refer to s3 which is indeed is out of scope, but it would reference s2 (and s1 is shadowed).Ladyinwaiting
Beautiful. The s4 case you added is interesting, and really surprising, what do you mean with "replaces s3"? If it had the same name (s3) I could see it but why would there be any interacting between two different variables (s3 and s4)? Why do you use the function f opposed to just test the values directly? The last assert doesn't use f(), not sure if if that was on purpose or an oversight.Ladyinwaiting
@AllanWind s3 and s4 go on stack, after s3 deallocates stack goes down, then s4 goes in the same location, O0 is really dumb and predictable. f was used in random places to show that lifetime stays for subcalls (kind of obvious because on stack).Business

© 2022 - 2024 — McMap. All rights reserved.