2

This is a rookie question, please bear with me. So the doubt is why f() function points different address. My understanding is the variable v must overwrite the old value.

package main
import "fmt"

var p = f()

func f() *int {
    v := 1
    return &v
}

func main() {
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(p)
}

//0xc0000140b0
//0xc0000140b8
//0xc0000140e0
//0xc000014098
2
  • 2
    "My understanding is the variable v must overwrite the old value." The language spec (which is worth reading!) doesn't make any guarantees in this case. No guarantee that the address stays the same nor that it must change. Even different compilers might do differently. Commented Nov 1, 2021 at 17:29
  • Nothing to do with the answer, but my compliments on getting the English-language expression bear with me correct. So many people (Americans especially) write "bare with me," which has a rather... different meaning. :-) Meanwhile, another minor English-language point: where you wrote "doubt", you mean "question". There's a subtle distinction between these: doubt has the connotation of disbelief, vs question which does not: doubt has an implied "possibly or even probably false" built into it. Commented Nov 2, 2021 at 1:22

2 Answers 2

5

The compiler detects that v escapes the function f, so it is allocated on the heap. Each call to f returns a new instance of v, that's why you see a different address for each call.

Sign up to request clarification or add additional context in comments.

4 Comments

This is indeed why the OP sees the result he sees now. However, since each instance of v has gone "dead" by the next call, the language specification allows the compiler to re-use the old address. This compiler simply isn't smart enough to do so.
If the GC were to run between invocations, it would have a chance to free the variable. In this case, it didn't have a chance to run. Would the compiler really do that, @torek, without knowing whether there are other calls to that function?
An omniscient (or at least sufficiently smart) compiler would know that fmt.Println isn't allowed to save the pointer, and hence while the value escapes from f to main, it does not escape from there, and hence the allocation itself can just be moved up to the stack frame for main itself. If f is inlined and fmt.Println is special-cased as "pure", simple escape analysis suffices and we need not run a GC at all.
(Note: I'm mis-using the usual compiler-specific meaning of pure here: a pure function is one without side effects, and output is a kind of side effect, so printing is inherently impure. But I think the meaning is clear enough.) Having the compiler analyse, at compile time, that fmt.Println is "pure" because everything it calls is likewise "pure" would be generally useful, and would then enable this kind of optimization.
1

To give a simple answer to this

Go looks for variables that outlive the current stack frame and then heap-allocates them

Basically, the variable v escapes the function f stack frame and gets allocated in heap which is why you see different addresses printed everytime.

Read this nice introduction to escape analysis. https://medium.com/a-journey-with-go/go-introduction-to-the-escape-analysis-f7610174e890

Try running escape analysis to see all the variables that got escaped.

go build -gcflags="-m" main.go:
./main.go:7:2: moved to heap: v   //points to v := 1
./main.go:12:15: moved to heap: v //points to fmt.Println(f())
./main.go:13:15: moved to heap: v //points to fmt.Println(f())
./main.go:14:15: moved to heap: v //points to fmt.Println(f())

Note that the last fmt.Println(f()) statement is not considered for escaping as value passed to Println is p which is a global variable so its already in heap and thus doesn't need to escape.

1 Comment

Thank you @anuraag_100 being a nobie this helped me alot

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.