Lifetime of a structure with no arrays in it

Viewed 132

Take this code:

#include <stdio.h>

struct S { int x; int y; };

struct S foo(int a, int b) {
    struct S s = { a, b };
    return s;
}

int main() {
    int a;

    a = foo(2, 4).x;
    printf("%d\n", a);
    return 0;
}

It works as intended. What I'm concerned about is the lifetime of the returned struct object. I know the standard talks about temporary lifetime for structs that contain arrays, but in this case there is no array in the structure.

So I guess that as soon as foo() has ended, its return value should be dead, right? But apparently we can still access the x member. Why?

4 Answers

"I know the standard talks about temporary lifetime for structs that contain arrays, but in this case there is no array in the structure."

You mean this paragraph:

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime.36) Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression ends. Any attempt to modify an object with temporary lifetime results in undefined behavior. An object with temporary lifetime behaves as if it were declared with the type of its value for the purposes of effective type. Such an object need not have a unique address.

36) The address of such an object is taken implicitly when an array member is accessed.

Source: ISO/IEC 9899:2018 (C18), §6.2.4/8

The temporary lifetime was explicitly invented in the context of structures and unions which contain array members because since array to pointer decay, accessing an array member by its name would give you a pointer to the first element of the array, which invoked undefined behavior in earlier C standard before this paragraph was added in C11.

Chris Dodd explained it a little better here.

So tempory lifetime (as meant in the standard) isn't relevant for structures with non-array members.

"So I guess that as soon as foo() has ended, its return value should be dead, right?"

No. foo() returns a copy of an object of struct S and not a reference to a local struct S object. Note that the return type of foo() is struct S, not struct S * (a pointer to struct S).

"But apparently we can still access the x member. Why?"

Because you return a copy to the caller. You attempt to access the member x of this copy and not the struct S object s inside of foo().

The call to foo(2, 4) returns a copy of the variable s inside the function.

This returned (and temporary) copy will have a lifetime to the end of the full expression, which is the assignment a = foo(2, 4).x.

This means that the assignment to a is done before the lifetime of the temporary structure ends, which means that the code you show is fine and valid.

You can read more about lifetime in e.g. this reference.

The structure returned by foo is just a value (also called an rvalue). It is not an object and does not have any lifetime.

Consider a function int foo(void) { return 3; }. This returns an int value of 3, and we would not expect to be able to take its address, as in printf("%p", (void *) &foo());. The 3 is just a value used in the computer with no associated storage.

Similarly, given struct S { int x, y; }, struct S foo(void) { return (struct S) { 3, 4 }; } returns a struct S value containing 3 and 4. Although we often think of structures as layouts of memory, the C standard treats this return value as just a value. It is a compound value, having multiple parts, but it is just a value with no associated storage. It is not an object in the C model.

Also similarly, given struct S { int x, y[1]; }, struct S foo(void) { return (struct S) { 3, { 4 } }; } returns a struct S value containing 3 and an array containing 4. Here the C standard painted itself into a corner. It wanted to support returning structures from functions, but, when you access an array, as in foo().y[0], the rule currently in C 2018 6.3.2.1 3 says the array is converted to a pointer to its first element. A pointer has to point to storage, so there has to be some object to point to. I suppose one solution might have been to say you cannot use arrays within such structure values individually. (You could use the return value by copying it into an object with struct S x = foo(); and then using x.) However, the solution the C committee adopted was to define a temporary lifetime for such structures. Their definition for that, in C 2018 6.2.4 8, defines a temporary lifetime only for structures and unions that contain an array member.

However, in your code, this is not a concern. Because your foo(2, 4) returns a value, you can use that value as you desire; foo(2, 4).x works because it takes the x member of the value. It does not need to worry about the lifetime of any object because there is no object involved.

From the C Standard

4 A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: an initializer that is not part of a compound literal; the expression in an expression statement; the controlling expression of a selection statement (if or switch); the controlling expression of a while or do statement; each of the (optional) expressions of a for statement; the (optional) expression in a return statement. There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.

The lifetime of a temporary object ends when the evaluation of the containing full expression ends.

So within this expression statement

a = foo(2, 4).x;

the returned object of the structure type is alive and the value of its data member x is assigned to the variable a that is declared in main and has the block scope of main.

Related