3

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?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Anti Earth
  • 4,671
  • 13
  • 52
  • 83
  • regarding: `Container getContainer() { Container c = {.arr = {{1,2,3}, {4,5,6}, {7,8,9}} }; return c; }` No, this is NOT fine. The variables located on the stack go out of scope when the containing code block exits. So referencing 'c' in the calling function may (or may not) work, depending on the size of 'c'. Much better to allocate some dynamic memory (for instance via `malloc()`) then return the pointer to that dynamic memory. – user3629249 Sep 02 '19 at 15:12
  • @user3629249 I believe you've misread the code. The caller happens to use the same variable `c`, but does not refer to the callee's local variables. – Anti Earth Jan 29 '20 at 15:34

4 Answers4

1

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

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.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
0

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;
}
haccks
  • 104,019
  • 25
  • 176
  • 264
  • I think it's a little disingenuous to say returning a primitive is the same as returning a struct, because that's part of the *magic* of the struct; that multiple primitives are bundled into one contiguous memory span. Furthermore, that the array is 'flattened' into the struct's space, rather than existing elsewhere on the stack and having its pointer stored in the struct, is further magic. Ergo how primitives are returned seems to have little relevance in understanding this struct behaviour. – Anti Earth Aug 31 '19 at 13:15
  • @Anti: In C, neither aggregates nor scalars are magic. C objects are just a sequence of bytes; you can copy those bytes elsewhere, and later copy them back, and the value is unchanged. A function can return a small aggregate in a single machine register, and it can return a scalar by copying its bytes individually into storage provided by the caller. – rici Aug 31 '19 at 15:45
  • @rici of course, but C storing array elements within that sequence, rather than keeping a pointer to the array elsewhere on the stack, is an orthogonal matter and critical to my question. That is to say, even *knowing* (as I clearly did) in my OP that structs are 'return-by-value', it is conceivable that array pointers in structs dangle after their return. So drawing analogy between structs and primitives does not at all answer my question. Thankfully, it's been otherwise answered :) – Anti Earth Aug 31 '19 at 16:05
  • @anti: you need to understand the difference between `int*`, which is a pointer, and `int[3]`, which is an array. That has nothing to do with structs. An array *pointer* in a struct is very different from an array in a struct. Understanding that difference is crucial. – rici Aug 31 '19 at 17:47
0

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.

dbush
  • 205,898
  • 23
  • 218
  • 273