Reputation: 4810
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
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
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
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
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