Reputation: 379
I'm trying an experiment in Go, to see what happens if I store a pointer to a variable on the stack, and then access that variable after the original variable has left scope.
package main
import "fmt"
var p chan bool;
// a temp struct
type v struct {
a int
}
func another_thread(vx *v) {
// this code should be executed after a() returns so vx should be a pointer to a value that's no longer on the stack
fmt.Printf("another_thread(): %p\n", vx);
vx.a = 4 // am I updating a dangling pointer that may have unintentional side effects??
fmt.Println(" - ", vx.a);
p<-true;
}
func update_v(vx *v) {
vx.a = 3;
fmt.Printf("update_v(): %p\n", vx);
go another_thread(vx)
}
func alloc_on_stack() {
// allocate v1 on the stack
var v1 v
v1.a = 1
fmt.Printf("alloc_on_stack(): %p\n", &v1);
// pass a pointer to v1 on the stack
update_v(&v1)
// print '3' to prove byref actually took it by reference
fmt.Println(" - ", v1.a);
// when the function returns, v1 should be popped off the stack
}
func main() {
p = make(chan bool)
alloc_on_stack();
fmt.Println("outside of alloc_on_stack, waiting");
<-p;
fmt.Println("done");
}
In alloc_on_stack, v1 is stored as a local variable on the stack. I pass a pointer to v1 to update_v, which passes it to another_thread. By another_thread doesn't execute until after alloc_on_stack finishes.
Yet, when I run that code, I don't get any errors, instead I see this:
alloc_on_stack(): 0x1043617c
update_v(): 0x1043617c
- 3
outside of alloc_on_stack, waiting
another_thread(): 0x1043617c
- 4
done
Shouldn't vx inside another_thread be a dangling pointer?
Upvotes: 8
Views: 3475
Reputation: 53398
Go doesn't distinguish between stack and heap as a language. Its implementations use escape analysis to prove that certain variables, even though they are referenced, can be placed on the stack safely. A naive implementation could just place all variables which are referenced in the heap.
You can use the -m flag on 6g to print out performance optimizations such as when it places something on the stack or heap.
Given your example:
$ go build -gcflags "-m" tmp.go
# command-line-arguments
./tmp.go:12: leaking param: vx
./tmp.go:14: another_thread ... argument does not escape
./tmp.go:16: another_thread ... argument does not escape
./tmp.go:20: leaking param: vx
./tmp.go:20: leaking param: vx
./tmp.go:20: leaking param: vx
./tmp.go:23: update_v ... argument does not escape
./tmp.go:30: moved to heap: v1
./tmp.go:33: &v1 escapes to heap
./tmp.go:36: &v1 escapes to heap
./tmp.go:33: alloc_on_stack ... argument does not escape
./tmp.go:39: alloc_on_stack ... argument does not escape
./tmp.go:45: make(chan bool, 0) escapes to heap
./tmp.go:47: main ... argument does not escape
./tmp.go:49: main ... argument does not escape
Upvotes: 7
Reputation: 181725
Nope. The Go compiler detects that you are taking the address of a local variable, and keeps it around until all references to it are gone. From then on, the variable can be garbage collected.
This is why stuff like this isn't just allowed, it's even idiomatic:
func foo() *Bar {
return &Bar{42, "frob"}
}
Upvotes: 16