新手问题 循环内临时变量问题

heml · 2019年06月12日 · 最后由 qiyin 回复于 2019年07月08日 · 594 次阅读

如题,以下代码, temp 每次都会分配新地址,还是有什么规则,有官方标准吗? 自测每次都会分配新地址。 (主要牵涉到闭包捕获问题,如果每次都分配新地址,可以放心的传入闭包)


for i := 0; i < 100; i++{
  temp := i
  fmt.Println("address of temp:%p", &temp)
  // call lamda(temp)
}
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio

这个问题有意思。 逃逸分析认为 temp 逃逸了,所以每个循环的 temp 都重新分配在堆上了。试试这个不逃逸版本:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    for i := 0; i < 100; i++ {
        temp := i
        p := uintptr(unsafe.Pointer(&temp))
        fmt.Printf("address of temp:%x\n", p)
    }
}

package main

func main() {
    for i := 0; i < 100; i++ {
        temp := i
        println(&temp)
    }
}

这样不会变

这个现象导致一下代码不保证符合预期,只能通过参数透传 “i” 到闭包才行? (temp 是否必然是一个新变量并没有明文规定)


func main() {
    cbList := make([]func(), 5)
    for i := 0; i < 5; i++{
        temp := i
        cbList[i] = func(){
            fmt.Println("I am i:", temp)
        }
    }

    for _, v := range cbList{
        v()
    }
}

循环括号 (block) 内的每次循环都是一个新的 scope,temp 变量在每次循环体内都是不同的变量(https://golang.org/ref/spec#Blocks)。这些不同的变量是否能安全复用同一个栈上地址属于编译器优化的范畴。

更有意思了,另一个例子:

尝试顺序和并发调用闭包,显然逃逸分析知道顺序的时候临时变量可以安全复用栈上的同一个地址,起 goroutine 的时候不能安全复用,只能分配在堆上。

package main

import (
    "fmt"
    "unsafe"  
    "sync"
)

func main() {
    wg := &sync.WaitGroup{}
    for i := 0; i < 100; i++ {
        temp := i
        wg.Add(1)
        go func() { // also try taking away "go"
            defer wg.Done()
            p := uintptr(unsafe.Pointer(&temp))
            fmt.Printf("%d %x\n", temp, p)
        }()
    }
    wg.Wait()
}

temp 可以安全的使用,没有做任何特殊的处理,可以理解为 temp 每次都是一个新的变量,编译器会根据你如何使用 temp 来决定是分配到堆上还是栈上的。

fmt.Printf 调用之后 temp 地址就会不一样,println 调用就会一样。

前文说的:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    for i := 0; i < 100; i++ {
        temp := i
        p := uintptr(unsafe.Pointer(&temp))
        fmt.Printf("address of temp:%x\n", p)
    }
}

这种避免 temp 逃逸,实际在编译过程已经处理好 uintptr(unsafe.Pointer(&temp)), 逃逸的对象不再是 temp 而是 p 变量,所以只是逃逸的成本不一样了而已。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册