-4

I know returning pointer to local variable is not a good idea, because the stack used for that variable will be reused. As so:

#include <stdio.h>

int *func(){
    int x = 10;
    return &x;
}

int main(){
    printf("%p\n",(void*)func());
}

print (nil) as expected.

However, why can I then return pointer to data of local struct? :

#include <stdio.h>

struct foo{
    char c;
    int *p;
};

int *func(){
    struct foo f;
    f.c = 'c';
    
    int local = 10;
    f.p = &local;
    return f.p;
}

int main(){
    int *b = func();
    printf("%d\n",*b);
}

The struct foo f is local in function func so there should be the same rule for every local variable - that is automatic storage in stack. I have not yet looked at assembler output of it to confirm that, but if this assumption is not true, then why is there special treating for struct types? Before I look to asm, is the storage of struct foo in stack?

Barmar
  • 741,623
  • 53
  • 500
  • 612
milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • 1
    It's not that you can't return the pointer. You can return the pointer. But if you use the pointer, you might find that it doesn't point the same data any more. – user253751 Jan 07 '21 at 00:51
  • I don't know why your first code returns null. It might be because of compiler optimization. Try with compiling `-O0` and then maybe it won't print nil. – user253751 Jan 07 '21 at 00:51
  • 1
    `print '(nil)' as expected` That's the wrong expectation. The behavior is undefined, and anything is allowed to happen. `gcc` [chooses to return NULL](https://stackoverflow.com/questions/40290049/why-does-gcc-return-0-instead-of-the-address-of-a-stack-allocated-variable) in that case, but it's not something that you can or should rely on. The second example is [undefined behavior](https://en.cppreference.com/w/c/language/behavior) as well, so there is no point in wodering why something in particular happens or does not happen, since *anything* is allowed to happen. – dxiv Jan 07 '21 at 00:52
  • @user253751 I cannot dereference pointer returned in first example - dereferencing null pointer. But I **can** dereference the pointer from second example. That will give right value of `10` – milanHrabos Jan 07 '21 at 00:52
  • 3
    It's undefined behavior, anything can happen, including something that looks like it's allowed. – Barmar Jan 07 '21 at 00:53
  • But you can't dereference it any more if you do some other stuff first. Try `int *b = func(); printf("hello world magic number is %d\n", 123); printf("%d\n", *b);` – user253751 Jan 07 '21 at 00:53
  • @dxiv why undefined behaviour? I am *not* dereferencing the pointer, I am *just* printing its value `%p`, that is not UB – milanHrabos Jan 07 '21 at 00:54
  • @Barmar which case? first or second? – milanHrabos Jan 07 '21 at 00:55
  • @milanHrabos The first example is simply printing the value of the pointer itself. It doesn't matter what that value is, printing the pointer is not UB (expecting the print to say nil, OTOH, is UB). The second example is dereferencing the pointer, which is UB since the pointer is not pointing at valid memory. – Remy Lebeau Jan 07 '21 at 00:56
  • @milanHrabos Both cases. – Barmar Jan 07 '21 at 00:56
  • 1
    @milanHrabos Whether simply printing the value is UB is an argument better left to the real language lawyers. But in any case, the *value* of the pointer is undefined, it can be anything, so "*expecting*" any particular value is a wrong expectation. – dxiv Jan 07 '21 at 00:57
  • In the first case, the compiler realizes that returning the address of a local variable is meaningless, so it apparently optimizes it out and returns a null pointer. In the second case, all the variable assignments make it difficult for it to tell that `f.p` is the address of a local variable, so it just returns it as is. – Barmar Jan 07 '21 at 01:00
  • @Barmar "all the variable assignments make it difficult for it to tell", well and the compiler is human with brain? I did not know compiler can *tell* when assignments are "difficult". Give the implementation of these "decisions" of compiler please then. – milanHrabos Jan 07 '21 at 01:04
  • @milanHrabos you can read the compiler source code to see how it makes decisions – M.M Jan 07 '21 at 01:04
  • @milanHrabos It's a process called "data flow analysis". – Barmar Jan 07 '21 at 01:16

1 Answers1

4

All of your examples cause undefined behaviour. The address of an object that no longer exists is an indeterminate value (C11 6.2.4/2) and trying to print an indeterminate value causes undefined behaviour.

See this question for more Standard references and discussion about the use of indeterminate values and rationale for the language rules.

Being undefined behaviour, you should not expect any particular behaviour, such as nil or otherwise.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Then why. I got perfectly valid result of `10` (after dereferencing returned pointer) in second example? UB will be if that would be null pointer, but it isn't. The null pointer is **only** in first example – milanHrabos Jan 07 '21 at 01:01
  • @milanHrabos outputting `10` is a possible manifestation of undefined behaviour. Please read the link in my answer to understand what "undefined behaviour" means. – M.M Jan 07 '21 at 01:02
  • manifestation of UB would be if it *was not* `10` – milanHrabos Jan 07 '21 at 01:05
  • 2
    @milanHrabos incorrect . UB means *undefined* behaviour, not "definitely different to my expectation behaviour" – M.M Jan 07 '21 at 01:08
  • Printing an indeterminate value does not cause undefined behavior. The question you cite refers to using the value of an uninitialized object (which does have indeterminate value) of automatic storage duration that does not have its address taken. That does have undefined behavior, but it does not apply here. – Eric Postpischil Jan 07 '21 at 02:05
  • @EricPostpischil quote from DR451, "Library functions will exhibit undefined behavior when used on indeterminate values." – M.M Jan 07 '21 at 02:27