乐趣区

关于go:关于golang的逃逸规则

逃逸规定

  1. 如果函数内部没有援用,则优先 (不是肯定) 放到栈中;
  2. 如果调配的内存超过了栈的存储能力,会在堆上调配
  3. 如果函数内部存在援用,则必然放到堆中;
  4. 一个援用类对象中的 any(interface{})进行赋值,会呈现逃逸景象。
  5. 一个援用类对象中的援用类成员进行赋值,会呈现逃逸景象。

个别咱们给一个援用类对象中的援用类成员进行赋值,可能呈现逃逸景象。能够了解为拜访一个援用对象实际上底层就是通过一个指针来间接的拜访了,但如果再拜访外面的援用成员就会有第二次间接拜访,这样操作这部分对象的话,极大可能会呈现逃逸的景象。Go 语言中的援用类型有 func(函数类型),interface(接口类型),slice(切片类型),map(字典类型),channel(管道类型),*(指针类型)等。

对于后面 3 条咱们很好了解,咱们当初来看第 4 点。

示例一 map 的 value 是 any

package main

func main() {data := make(map[int]interface{})
    data[100] = 200
}

//go build -gcflags=-m .\main.go
//main.go:3:6: can inline main
//main.go:4:14: make(map[int]interface {}) does not escape
//main.go:5:14: 200 escapes to heap

后果是 200 逃逸到堆内存中去了。

示例二 map 里的 key 和 value 类型都确定

package main

func main() {data := make(map[int]int)
    data[100] = 200
}

/*
go build -gcflags=-m .\main.go
# command-line-arguments
./main.go:3:6: can inline main
./main.go:4:14: make(map[int]int) does not escape
*/

后果是没有产生逃逸

示例三 map 的 key 和 value 都未确定类型

package main

func main() {data := make(map[any]any)
    data[100] = 200
}

/*
go build -gcflags=-m .\main.go
# command-line-arguments
./main.go:3:6: can inline main
./main.go:4:14: make(map[any]any) does not escape
./main.go:5:7: 100 escapes to heap
./main.go:5:14: 200 escapes to heap
*/

因为 map 自身是一个援用类型,而当其键和值转换为 interface{}类型,这导致了 100 和 200 的逃逸到堆上。编译器无奈确定这两个值的具体类型,因而它将它们调配到堆上以确保安全性。在示例三种编译器能够确定 key 和 value 的类型都是 int,就将其调配到了栈上。

对于第五点咱们再来看几个示例:

示例四 map 外部应用切片

package main

func main() {data := make(map[string][]string)
    data["key"] = []string{"value"}
}

//main.go:3:6: can inline main
//main.go:4:14: make(map[string][]string) does not escape
//main.go:5:24: []string{...} escapes to heap

示例五 切片

package main

func main() {data := []string{"value"}
    data = append(data, "")
}

/*
./main.go:3:6: can inline main
./main.go:4:18: []string{...} does not escape
*/

如果 data 自身是个 map(援用类型),在 map 外面的 value 又是一个 slice(切片类型)也是援用类型,那这个切片就会溢出。

示例六 切片外部应用指针

package main

func main() {
    a := 10
    data := []*int{nil}
    data[0] = &a
}

/*
./main.go:3:6: can inline main
./main.go:4:2: moved to heap: a
./main.go:5:16: []*int{...} does not escape
*/

如果 data 自身是个切片(援用类型),在 data 外面的 value 又是一个int 也是援用类型,那这个int 类型的 a 就会溢出。

示例七 通道外部应用援用类型

package main

func main() {ch := make(chan []string)

    s := []string{"aceld"}

    go func() {ch <- s}()}

/*
./main.go:8:5: can inline main.func1
./main.go:6:15: []string{...} escapes to heap
./main.go:8:5: func literal escapes to heap
*/

示例八 函数参数应用援用类型

package main

import "fmt"

func foo(a *int) {return}

func main() {
    data := 10
    f := foo
    f(&data)
    fmt.Println(data)
}
/*
./main.go:5:6: can inline foo
./main.go:12:3: inlining call to foo
./main.go:13:13: inlining call to fmt.Println
./main.go:5:10: a does not escape
./main.go:13:13: ... argument does not escape
./main.go:13:14: data escapes to heap
*/
退出移动版