1

From what I know, local variables (not declared using the static keyword) within functions only exist for as long as the function is on the stack frame and when control returns to the caller all local variables are destroyed. This would explain why it is not possible to return a locally defined array from a function. Trying to dereference such an array returned would give us garbage values at best or a segfault at worst. Shouldn't this also be true for structures? That is, if we are returning a structure from a function, shouldn't trying to access its members also give us garbage values or a segfault?

I have the following two files for demonstrating. The first one is for arrays:

/* return_local_vs_malloc_array.c */
#include <stdio.h>
#include <stdlib.h>

#define LEN 10
#define VAL 100

int *return_local() {
    int arr[LEN] = {VAL};
    return arr;
}

int *return_mallocated() {
    int *arr = (int *)malloc(sizeof(int)*LEN);
    for (int i = 0; i < LEN; i++) 
        arr[i] = VAL;
    return arr;
}

void display(int *arr) {
    for (int i = 0; i < LEN; i++) {
        printf("%d ", arr[i]);
    }
    putchar('\n');
}

int main(void) {
    int *x = return_local();
    fputs("local: ", stdout); display(x);

    int *y = return_mallocated();
    fputs("malloc: ", stdout); display(y);
    free(y);

    return 0;
}

and the other one is for structs:

/* return_local_vs_malloc_struct.c */
#include <stdio.h>
#include <stdlib.h>

struct foobar {
    int foo;
    int bar;
};

struct foobar return_local(int foo, int bar) {
    struct foobar f;
    f.foo = foo;
    f.bar = bar;
    return f;
}

struct foobar *return_mallocated(int foo, int bar) {
    struct foobar *f = (struct foobar *)malloc(sizeof(struct foobar));
    f->foo = foo;
    f->bar = bar;
    return f;
}

int main(void) {
    struct foobar x = return_local(10, 20);
    printf("local: x = {foo: %d, bar: %d}\n", x.foo, x.bar);

    struct foobar *y = return_mallocated(10, 20);
    printf("malloc: y = {foo: %d, bar: %d}\n", y->foo, y->bar);
    free(y);

    return 0;
}

When I run this I get:

$ tcc -run ./return_local_vs_malloc_array.c
local: x = -1519296757 32767 0 0 -849137592 32767 -849137536 7 -849137608 32767
malloc: y = 100 100 100 100 100 100 100 100 100 100

~/
$ tcc -run ./return_local_vs_malloc_struct.c
local: x = {foo: 10, bar: 20}
malloc: y = {foo: 10, bar: 20}

As you can see, the locally allocated structure persists even when control has returned from the function in which it was declared. From what I know, this is not supposed to happen and the only way one can make a local variable persist beyond the lifetime of the called function is by using the static qualifier. But maybe I am wrong?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
First User
  • 704
  • 5
  • 12
  • 2
    A struct is copied when you return it. Pointers are copied as well, but not the data they point to which is why the local array example is wrong. – Retired Ninja Jul 30 '21 at 08:49
  • 2
    `int *return_local() {` ***DOESN'T*** return an array, though! – user253751 Jul 30 '21 at 08:54
  • In the first example, return_local returns a pointer to an array that is alive only in the function return_local or is allocated on the function's stack. So using the value returned by return_local is at the very least cause of UB. – Sir Jo Black Jul 30 '21 at 09:43
  • Yes. That is how it works. – Martin James Jul 31 '21 at 09:12

2 Answers2

4

When a function returns a struct (or any other type of variable) by value, then a copy of that variable or structure is made, and that copy is returned to the caller. So, even though the 'original' has ceased to exist when the function ends, the copy is still valid.

However, when a function returns an array, what is actually returned is the address of the first element of that array – see What is array to pointer decay? So, in such cases, when the function has completed, the local array no longer 'exists' and the address returned becomes invalid.

You can 'emulate' this behaviour for a struct by redefining your function to return the address of the local variable, like so:

struct foobar {
    int foo;
    int bar;
};

// Note: BAD function, for example purpose only. DO NOT USE IT!
struct foobar* return_local(int foo, int bar) // Return a pointer-to-struct
{
    struct foobar f;
    f.foo = foo;
    f.bar = bar;
    return &f; // Any caller that attempts to use this returned pointer will
               // exhibit Undefined Behaviour, as the 'f' struct will be gone!
}

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
1

You can not return an array because arrays can not be used as an initializers of other arrays. That is you can not write for example

int arr1[LEN] = {VAL};
int arr2 = arr1;

So when you use an array designator as an expression of a return statement it is implicitly converted to pointer to its first element. That is it is the pointer that is returned not the array itself. And the pointer becomes invalid because the array with the automatic storage duration pointed to by the pointer will not be alive after exiting the function.

On the other hand, one object of a structure type can be used as an initializer of another object of the structure type. For example

struct foobar f1 = { .foo = 10, .bar = 20 };
struct forbar f2 = f1;

So when an object of a structure type is returned from a function then there is created a copy of the returned object that can be assigned to an object of the structure type in the caller of the function.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335