1

I define a structure with a single field and an init function like so:

typedef struct{
  int* field;
}myStruct;
myStruct init(int x){
  myStruct s;
  s.field = &x;
  return s;
}

I then declare two of these structure and then print the field of the first.

int main(){
  myStruct s1 = init(1);
  myStruct s2 = init(2);
  printf("s1.field=%d\n",*s1.field);
  return 0;
}

The result of this is "s1.field=2". Why does the initialization of the second affect the first?

Mikkel bruun
  • 129
  • 1
  • 10
  • 7
    undefined behaviour: `int x` is a parameter, like a local variable, you cannot take the reference and expect it to keep the data. It's auto memory, so it's reused when init exits. – Jean-François Fabre Aug 06 '18 at 19:44
  • 4
    You are storing the address of a local variable. It's life time ends when the init function returns. Thus you are invoking undefined behaviour. – Ajay Brahmakshatriya Aug 06 '18 at 19:44
  • 1
    you get the previous value because you're calling the same function. Call another function (with another set of parameters/stack frame) and you'll get garbage. – Jean-François Fabre Aug 06 '18 at 19:50
  • 3
    Possible duplicate of [Returning a pointer to an automatic variable](https://stackoverflow.com/questions/1224042/returning-a-pointer-to-an-automatic-variable) – too honest for this site Aug 06 '18 at 21:42
  • @Jean-FrançoisFabre: There is no guarantee which value is returned. It's undefined behaviour (see the dupe), so anything can happen, just wait for nasal daemons. – too honest for this site Aug 07 '18 at 10:52
  • agreed, it's UB, in that case "you get the previous value because you're calling the same function" and the compiler uses the same stackframe. But that's just the explanation. The fact that it returns a "valid" value and not just garbage probably confused OP even more. – Jean-François Fabre Aug 07 '18 at 12:29

3 Answers3

2

You're calling this function twice:

myStruct init(int x){
  myStruct s;
  s.field = &x;
  return s;
}

On both calls, the variable (or parameter) x is allocated, but not valid anymore when the function returns. You've stored the address of a local variable, and since you're using the same function, the local variable address is the same the second time, which explains the "magic" (which is still undefined behaviour but most compilers using stack for auto variables will yield the same result)

Unfortunately compilers aren't smart enough to detect that you're storing an address of a local variables. They usually trigger warnings when you're returning an address on a local (it's easier to detect).

For instance if you call printf, you will get not 1 or 2 but complete garbage as the parameter memory won't be organized the same way as your function.

A clean way would be to allocate dynamic memory:

s.field = malloc(sizeof(*s.field));  // this memory persists even after the end of "init"
*s.field = x;  // copying the value, not the address

Of course it needs deallocation when structure isn't used.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • The question is just another variant of the well know dupe. VtC. – too honest for this site Aug 06 '18 at 21:46
  • except that it's not _returning_ the pointer (which triggers a compiler warning), but _storing_ it. – Jean-François Fabre Aug 06 '18 at 21:56
  • It returns a `struct` with the pointer. That's no relevant difference. – too honest for this site Aug 06 '18 at 22:03
  • the main difference is the lack of warning, the compiler isn't smart enough to see that you're storing the variable, as opposed to when you _return_ it. – Jean-François Fabre Aug 07 '18 at 07:36
  • That doesn't change the fact it's a dupe. There is no requirement for the compiler to warn. Consider the following: `auto int a= 40, *p = &a, *q = p; return q;` I doubt the compiler will warn about this either (if it does, add another ring to the chain). Would you consider this also not a dupe? How about another ring? All seperate questions? It's better to understand the underlying problem, which is the general problem of the users asking such questions and **trying** to learn C from YT. – too honest for this site Aug 07 '18 at 10:50
  • well, unlike for your music recommendations, I respectfully disagree. That was your right to vote to close. You know I like using the dupehammer when I agree. – Jean-François Fabre Aug 07 '18 at 12:31
  • @toohonestforthissite dupe this: https://stackoverflow.com/questions/51733664/explain-the-output-and-why-does-y-not-point-to-garbage-value :) – Jean-François Fabre Aug 07 '18 at 19:03
1

Any variable has some space in the memory. A pointer references that space. The space that local variables occupies is deallocated when the function call returns, meaning that it can and will be reused for other things. As a consequence, references to that space are going to wind up pointing to something completely unrelated.

The preferable way would be to use malloc() to reserve non-local memory. the danger here is that you have to deallocate (free()) everything you allocated using malloc(), and if you forget, you create a memory leak.

kiran Biradar
  • 12,700
  • 3
  • 19
  • 44
  • 1
    "Any variable has some space in the memory. A pointer references that space." --> Aside from those pesky "register" ones. – chux - Reinstate Monica Aug 06 '18 at 20:07
  • "Any variable has some space in the memory." - This is not guaranteed by the standard. There is not even a guarantee to use a register, as C does not really know about CPU registers, A modern compiler will possibly completely eliminate a variable out of existence for example. – too honest for this site Aug 06 '18 at 21:48
1

In the function

myStruct init(int x){
  myStruct s;
  s.field = &x;
  return s;
}

you are assigning the address of x to s.field. The problem is that x is local to the function, and once the function exits, ceases to exist (logically speaking - obviously, the memory location it occupied is still there, but it's now available for something else to use and may be overwritten). That pointer value is now invalid, and attempting to dereference it leads to undefined behavior.

What's most likely happening in this particular case is that the space that was used for x in the init(1) call is being reused and overwritten in the init(2) call, and miraculously that location is either not being overwritten by the printf call, or printf is writing a 2 to that location as part of its operations.

John Bode
  • 119,563
  • 19
  • 122
  • 198