Anti Earth
Anti Earth

Reputation: 4810

Do arrays in struct fields always persist upon return?

I know that I can't return a locally declared array from a function, because the array lives in the function's stack space and after returning, the pointer will dangle.

I.e. this is invalid

int* test() {
    int x[3] = {1,2,3};
    return x;
}

main() {
    int* x = test();
}

Therefore I'm nervous about returning structs which contain arrays; since they are just made part of the struct's contiguous memory space, are copied on return.

I.e. this is totally fine

typedef struct Container {
    int arr[3][3];
} Container;
Container getContainer() {
    Container c = {.arr = {{1,2,3}, {4,5,6}, {7,8,9}} };
    return c;
}

int main() {
    Container c = getContainer();
    // c.arr is "deep copied" to main's stack space
}

Still, I have a deep instinct to instead do do something like

void populateContainer(Container* c) {
    int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    memcpy(c->arr, arr, 3*3 * sizeof(arr));
}

int main() {
    Container c;
    populateContainer(&c);
}

So my question is, should I just trust that the array will always be safely copied-by-value when the struct is returned, and avoid the latter pattern? I'm always using the C99 standard. Are there compilers which wouldn't respect this, and for which I should use the uglier-but-seemingly-safer address-passing pattern?

Upvotes: 3

Views: 154

Answers (4)

Marco Bonelli
Marco Bonelli

Reputation: 69367

As per 6.9.1p3:

The return type of a function shall be void or an object type other than array type.

Regarding what an object type is defined to be, from 6.2.5p1:

The meaning of a value stored in an object or returned by a function is determined by the type of the expression used to access it. (An identifier declared to be an object is the simplest such expression; the type is specified in the declaration of the identifier.) Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes).

Also see this question about object types.


In conclusion, since this is a valid object type:

struct Container {
    int arr[3][3];
};

If your compiler is compliant with the standard, you can expect Container c = getContainer(); to work even when the Container is created inside the stack of the function and then returned, since the standard does not specify the origin of the returned value.

Upvotes: 1

n. m. could be an AI
n. m. could be an AI

Reputation: 120051

So my question is, should I just trust that the array will always be safely copied-by-value when the struct is returned, and avoid the latter pattern?

Absolutely yes, for any past, present, and foreseeable future generation of the C standard.

Are there compilers which wouldn't respect this, and for which I should use the uglier-but-seemingly-safer address-passing pattern?

No C compiler does that, by definition (if a compiler does that, then it's not a C compiler).

Longer explanation follows.

You cannot assign an array or initialise an array from another array or return an array or pass an array to a function. None of this works because arrays decay to pointers in most contexts. (When you are passing an array to a function, you are actually passing a pointer to its first element).

But you can perfectly do any of these things with any kind of data which is not an array, even if there are arrays inside it. Arrays inside data structures do not decay, neither do (surprise) pointers to arrays. There is simply no sensible way for them to do so.

Upvotes: 1

dbush
dbush

Reputation: 224457

The reason you can't return an array from a function is that in most contexts an array is converted to a pointer to the first element, so what you're really doing is returning a pointer to a local variable which is undefined behavior.

No such conversion happens when you return a struct from a function. The entire struct is returned by value, including any array it may contain.

So doing this:

Container getContainer() {
    Container c = {.arr = {{1,2,3}, {4,5,6}, {7,8,9}} };
    return c;
}

int main() {
    Container c = getContainer();
    // c.arr is "deep copied" to main's stack space
}

is perfectly safe.

Upvotes: 0

haccks
haccks

Reputation: 106092

Consider a basic example:

void populateInt(int *a){
    int x = 5;
    *a = x;
}

int main(void){
    int var;
    populateInt(&var);

    return 0;
}

The life of the variable x in the function populateInt will be end once function return. What will happen to var in the main? Is it populated or not?

The answer is var will be populated with the value of x in the populateInt function. The same holds true for the last example of your code.

This will also work

void populateContainer(int a[][3]) {
    int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    memcpy(a, arr, 3*3 * sizeof(int));
}

int main(void) {
    int arr[3][3];
    populateContainer(arr);

    return 0;
}

Upvotes: 0

Related Questions