简体   繁体   English

是否在Go函数中返回一个本地数组?

[英]Is returning a slice of a local array in a Go function safe?

What happens if I return a slice of an array that is a local variable of a function or method? 如果我返回一个作为函数或方法的局部变量的数组的片段会发生什么? Does Go copy the array data into a slice create with make() ? Go是否将数组数据复制到使用make()创建的切片中? Will the capacity match the slice size or the array size? 容量是否与切片大小或数组大小匹配?

func foo() []uint64 {
    var tmp [100]uint64
    end := 0
    ...
    for ... {
        ...
        tmp[end] = uint64(...)
        end++
        ...
    }
    ... 
    return tmp[:end]
} 

This is detailed in Spec: Slice expressions . 这在Spec:Slice表达式中有详细说明。

The array will not be copied, but instead the result of the slice expression will be a slice that refers to the array. 不会复制数组,而是切片表达式的结果将是引用数组的切片。 In Go it is perfectly safe to return local variables or their addresses from functions or methods, the Go compiler performs an escape analysis to determine if a value may escape the function, and if so (or rather if it can't prove that a value may not escape), it allocates it on the heap so it will be available after the function returns. 在Go中,从函数或方法返回局部变量或其地址是完全安全的,Go编译器执行转义分析以确定某个值是否可以转义函数,如果是,(或者更确切地说,如果它不能证明某个值)可能无法转义),它会在堆上分配它,以便在函数返回后可用。

The slice expression: tmp[:end] means tmp[0:end] (because a missing low index defaults to zero). 切片表达式: tmp[:end]表示tmp[0:end] (因为缺少的low索引默认为零)。 Since you didn't specify the capacity, it will default to len(tmp) - 0 which is len(tmp) which is 100 . 由于您未指定容量,因此默认为len(tmp) - 0 ,即len(tmp)100

You can also control the result slice's capacity by using a full slice expression , which has the form: 您还可以使用完整切片表达式控制结果切片的容量,该表达式具有以下形式:

a[low : high : max]

Which sets the resulting slice's capacity to max - low . 这将得到的切片容量设置为max - low

More examples to clarify the resulting slice's length and capacity: 更多示例来阐明生成的切片的长度和容量:

var a [100]int

s := a[:]
fmt.Println(len(s), cap(s)) // 100 100
s = a[:50]
fmt.Println(len(s), cap(s)) // 50 100
s = a[10:50]
fmt.Println(len(s), cap(s)) // 40 90
s = a[10:]
fmt.Println(len(s), cap(s)) // 90 90

s = a[0:50:70]
fmt.Println(len(s), cap(s)) // 50 70
s = a[10:50:70]
fmt.Println(len(s), cap(s)) // 40 60
s = a[:50:70]
fmt.Println(len(s), cap(s)) // 50 70

Try it on the Go Playground . Go Playground尝试一下。

Avoiding heap allocation 避免堆分配

If you want to allocate it on the stack, you can't return any value that points to it (or parts of it). 如果要在堆栈上分配它,则不能返回指向它的任何值(或部分值)。 If it would be allocated on the stack, there would be no guarantee that after returning it remains available. 如果它将被分配在堆栈上,则无法保证在返回后它仍然可用。

A possible solution to this would be to pass a pointer to an array as an argument to the function (and you may return a slice designating the useful part that the function filled), eg: 一个可能的解决方案是将指向数组的指针作为函数的参数传递(并且您可以返回指定函数填充的有用部分的切片),例如:

func foo(tmp *[100]uint64) []uint64 {
    // ...
    return tmp[:end]
}

If the caller function creates the array (on the stack), this will not cause a "reallocation" or "moving" to the heap: 如果调用函数创建数组(在堆栈上),这将不会导致“重新分配”或“移动”到堆:

func main() {
    var tmp [100]uint64
    foo(&tmp)
}

Running go run -gcflags '-m -l' play.go , the result is: 运行go run -gcflags '-m -l' play.go ,结果是:

./play.go:8: leaking param: tmp to result ~r1 level=0
./play.go:5: main &tmp does not escape

The variable tmp is not moved to heap. 变量tmp不会移动到堆。

Note that [100]uint64 is considered a small array to be allocated on the stack. 请注意, [100]uint64被认为是要在堆栈上分配的小数组。 For details see What is considered "small" object in Go regarding stack allocation? 有关详细信息,请参阅Go中有关堆栈分配的“小”对象?

Nothing wrong happens. 没有错。

Go does not make a copy but compiler performs escape analysis and allocates the variable that will be visible outside function on heap. Go不进行复制,但编译器执行转义分析并分配在堆上函数外部可见的变量。

The capacity will be the capacity of underlying array. 容量将是底层阵列的容量。

The data is not copied. 数据不会被复制。 The array will be used as the underlying array of the slice. 该数组将用作切片的基础数组。

It looks like you're worrying about the life time of the array, but the compiler and garbage collector will figure that out for you. 看起来你担心数组的生命周期,但编译器和垃圾收集器会为你解决这个问题。 It's as safe as returning pointers to "local variables". 它与返回指向“局部变量”的指针一样安全。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM