存储指向堆栈值的指针(Golang)

huangapple go评论82阅读模式
英文:

Storing a pointer to a stack value (Golang)

问题

我正在尝试一个Go语言的实验,看看如果我在堆栈上存储一个变量的指针,然后在原始变量离开作用域后访问该变量,会发生什么。

alloc_on_stack函数中,v1被存储为堆栈上的局部变量。我将v1的指针传递给update_v函数,然后再传递给another_thread函数。但是,another_thread函数直到alloc_on_stack函数执行完毕后才执行。

然而,当我运行这段代码时,我没有收到任何错误,而是看到以下输出:

alloc_on_stack(): 0x1043617c
update_v(): 0x1043617c
 -  3
outside of alloc_on_stack, waiting
another_thread(): 0x1043617c
 -  4
done

难道another_thread函数内部的vx不应该是一个悬空指针吗?

英文:

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?

答案1

得分: 14

不行。Go编译器会检测到你正在获取一个局部变量的地址,并在所有对它的引用消失之前保留它。从那时起,该变量可以进行垃圾回收。

这就是为什么像这样的代码不仅被允许,而且还是惯用的方式:

func foo() *Bar {
  return &Bar{42, "frob"}
}
英文:

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"}
}

答案2

得分: 7

Go语言在语言层面上不区分堆栈和堆。它的实现使用逃逸分析来证明某些变量,即使被引用,也可以安全地放在堆栈上。一个简单的实现可以将所有被引用的变量都放在堆上。

你可以在6g上使用-m标志来打印出性能优化信息,比如何时将某些东西放在堆栈或堆上。

以你的示例为例:

$ 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
英文:

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

huangapple
  • 本文由 发表于 2015年2月13日 02:24:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/28484976.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定