在Go函数中返回局部数组的切片安全吗?

如果我返回作为函数或方法的局部变量的数组的切片会发生什么? Go是否会将数组数据复制到使用 make()</ code>创建的切片中? 容量会匹配切片大小还是数组大小?</ p>

  func foo()[] uint64 {
var tmp [100] uint64
end:= 0
...
for ... {
...
tmp [end] = uint64(...)
end ++
...
}
...
返回tmp [: 结束]
}
</ code> </ pre>
</ div>

展开原文

原文

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()? 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]
} 

3个回答

This is detailed in Spec: Slice expressions.

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.

The slice expression: tmp[:end] means tmp[0:end] (because a missing low index defaults to zero). Since you didn't specify the capacity, it will default to len(tmp) - 0 which is len(tmp) which is 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.

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.

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), e.g.:

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:

./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.

Note that [100]uint64 is considered a small array to be allocated on the stack. For details see What is considered "small" object in Go regarding stack allocation?

dongliao2011
dongliao2011 让我们继续聊天中的讨论。
3 年多之前 回复
douxin8749
douxin8749 检查我编写的代码。 甚至在堆栈上也分配了一个11 uint64数组。 用字节替换uint64,您会看到它已经分配在堆栈上(显然)。
3 年多之前 回复
dongnaopa6200
dongnaopa6200 如果您阅读链接的答案,则表明限制大约为64 KB,之后将在堆上分配变量(即使没有转义)。 [100] uint64现在接近该限制。 如果返回指向它的切片,则视为“转义”。
3 年多之前 回复
doudu2515
doudu2515 我按照您的建议在操场上进行了测试,该阵列显然未分配到堆栈上。 我在代码的另一个地方使用的是数组[11] byte。 我怀疑go可以通过goroutines来保持堆栈较小的策略。 这是有道理的。
3 年多之前 回复
duanjing4623
duanjing4623 您可以返回传递的数组的一个切片,该切片指定_useful部分(已填充),这不是问题。 编辑清楚。 同样,这被认为是一个小数组,它将被分配在堆栈上。 有关详细信息,请参阅有关堆栈分配的Go中什么被视为“小”对象?
3 年多之前 回复
dongy44342
dongy44342 谢谢你的建议。 但这不符合我的目标。 函数foo正在生成一些要在切片中返回的数据,例如,我知道它永远不会超过100。 问题是我不预先知道所产生的数据量,通常它将远远少于100。大约10左右。 我假设数组是在堆栈上分配的。 这个假设是错误的。 小数组是。 没有那么大的数组。
3 年多之前 回复
douzhenchun6782
douzhenchun6782 参见编辑后的答案。 您可以选择将指针传递给数组作为参数。
3 年多之前 回复
donglingsai2880
donglingsai2880 这是一个非常详细的答案。 让我感到困惑的是我的假设,即数组将在堆栈上分配并在返回时像在C和C ++中那样释放。 如果它是在堆上分配的,则使用此代码模式不会有任何好处。 除了使用递归函数使用堆栈并仅分配一次且仅分配一次恰好需要的大小之外,还有其他解决方案吗?
3 年多之前 回复



不复制数据。 该数组将用作切片的基础数组。</ p>

您似乎担心该数组的生存时间,但是编译器和垃圾回收器将为您解决这个问题 您。 就像返回指向“局部变量”的指针一样安全。</ p>
</ div>

展开原文

原文

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

dongqiaogouk86049
dongqiaogouk86049 我假设数组是在堆栈上分配的。 仅对于小型阵列是这种情况。 上面的大数组是在堆上分配的,因此,的确,一个片将携带它,否则会浪费很多内存空间,除非我将相关数据复制到自己分配的片中。 但是该函数将分配两个对象,这是要避免的。 因此,首先使用该数组是一个错误的选择。
3 年多之前 回复



没有错。</ p>

Go不会复制,但是编译器执行转义分析,并分配在堆上的函数外部可见的变量。</ p>

< p>容量将是基础数组的容量。</ p>
</ div>

展开原文

原文

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.

The capacity will be the capacity of underlying array.

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问