Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing a pointer to a stack value (Golang)

Tags:

pointers

go

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?

like image 543
Andrew Price Avatar asked Feb 12 '15 18:02

Andrew Price


People also ask

Can you have a pointer to the stack?

A pointer variable (which is just a variable that contains the memory address of something else), might reside in the stack, in the heap, or in the static/global area. In other words, a pointer variable could reside anywhere in memory that any type of variable might reside. Pointer variables are just variables.

Can pointer variables store value?

A pointer variable (or pointer in short) is basically the same as the other variables, which can store a piece of data. Unlike normal variable which stores a value (such as an int, a double, a char), a pointer stores a memory address. Pointers must be declared before they can be used, just like a normal variable.

Why do we use * in Golang?

In Go a pointer is represented using the * (asterisk) character followed by the type of the stored value. In the zero function xPtr is a pointer to an int . * is also used to “dereference” pointer variables. Dereferencing a pointer gives us access to the value the pointer points to.

Can you use pointers in Go?

Using pointers in Go If we want to make the first code snippet work, we can make use of pointers. In Go, every function argument is passed by value, meaning that the value is copied and passed, and by changing the argument value in the function body, nothing changes with the underlying variable.


2 Answers

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"}
}
like image 128
Thomas Avatar answered Nov 15 '22 06:11

Thomas


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
like image 23
Stephen Weinberg Avatar answered Nov 15 '22 04:11

Stephen Weinberg