Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the C standard keep the return struct of a function on the stack if I keep a pointer to a value inside it?

Take this arguably questionable code.

struct X {
     int arr[1];
     float something_else;
};
    
struct X get_x(int first)
{
     struct X ret = { .arr = { first } };
     return ret;
}

int main(int argc, char **argv) {
    int *p = get_x(argc+50).arr;
    
    return *p;
}

get_x returns a struct X. I'm only interested in its member arr. Why would I make a local variable for the entire struct if I only want arr...

But.. is that code correct?

In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?

like image 727
aganm Avatar asked Mar 26 '21 03:03

aganm


People also ask

Can C function return struct?

Now, functions in C can return the struct similar to the built-in data types.

Are structs pass by value in C?

A struct can be either passed/returned by value or passed/returned by reference (via a pointer) in C. The general consensus seems to be that the former can be applied to small structs without penalty in most cases.

Should I return a struct or a pointer?

There are two ways of "returning a structure." You can return a copy of the data, or you can return a reference (pointer) to it. It's generally preferred to return (and pass around in general) a pointer, for a couple of reasons. First, copying a structure takes a lot more CPU time than copying a pointer.

Does returning a struct copy it?

Because an object created locally within a function is temporary and goes out of scope when the function returns, a returned object is never copied with c++11 onward.


2 Answers

What you're doing is not allowed by the standard.

The struct returned from the function has temporary lifetime which ends outside of the expression it is used in. So right after p is initialized, it points to an object whose lifetime has ended and its value becomes indeterminate. Then attempting to dereference p (which is now indeterminate) in the following statement triggers undefined behavior.

This is documented in section 6.2.4p8 of the C standard:

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. 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 or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.

Where the lifetime of an object and what happens to a pointer to an object when its lifetime ends is specified in section 6.2.4p2:

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime

If you were to assign the return value of the function to an instance of struct X, then you can safely access the arr member of that instance.

like image 157
dbush Avatar answered Nov 06 '22 16:11

dbush


In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?

No, it cannot ever do this, even if it "knew" to do so. Things are popped off the stack when a function returns, and the contents of anything "above" that point become undefined.

Even so,

But.. is that code correct?

That part is! This is because you are not creating a pointer to the struct that was in the callee's stack frame. You are creating a pointer to a copy, which was implicitly created when you returned a struct by value.

Conceptually, the code will copy this struct into space reserved in the caller's stack frame (because you're specifically calling a function that returns a struct, in the general case the value can't be returned in a register). In practice, an optimizing compiler might return it in a register (if your machine's registers can fit a struct containing an int and a float), construct it directly in place in the caller's stack frame (the right location can easily be found as an offset from the base of the callee's stack frame), shuffle memory around (a destructive overlapping-move operation is acceptable exactly because of the "memory contents are now undefined" thing), etc.

... But only that part, as pointed out by @dbush. To create a copy properly (i.e., with a long enough lifetime to use this way), the return value from the function would need to be an lvalue. Conceptually, the compiler is allowed to pop that copy off the stack once it's done retrieving the .arr member. In practice, the stack pointer wouldn't get adjusted, but an optimizing compiler would consider that part of the stack free to use for other local variables.

like image 42
Karl Knechtel Avatar answered Nov 06 '22 17:11

Karl Knechtel