Go语言中的不安全指针:函数调用结束会导致数组失效。

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

unsafe Pointers in Go: function call end kills array

问题

我正在为一个库编写代码,我想要返回一个未指定类型的数组(或者向一个数组写入数据)给调用者。这个类型可能会有所不同,取决于谁来调用 - 不过,我可以在我的函数内部创建多个该类型的对象。一种方法是调用者创建一个数组,然后被调用者填充该数组 - 但是,无法确定这个数组的长度。 (被调用者是否可以使调用者的数组变大?请记住,被调用者只能看到 x interface{}...)

另一种方法是我选择的方法,因为我不知道如何实现上述的方法,即调用者给我他特定类型的指针,然后我将其重定向到我创建的对象数组。

下面是我的解决方案。我的问题是:为什么函数调用后数组为空?它们在我的操作之后指向同一个数组,它们应该是相同的。我有什么遗漏吗?我考虑过垃圾回收,但它不可能那么快,对吗?

package main

import "unsafe"
import "reflect"
import "log"

func main() {
    var x []string
    log.Printf("before: %v, %p", x, x)
    manipulate(&x)
    log.Printf("after: %v, %p", x, x)
}

func manipulate(target interface{}) {
    new := make([]string, 0, 10)
    new = append(new, "Hello", "World")
    log.Printf("new: %v, %p", new, new)
    p := unsafe.Pointer(reflect.ValueOf(target).Pointer())
    ptr := unsafe.Pointer(reflect.ValueOf(new).Pointer())
    *(*unsafe.Pointer)(p) = ptr
}

链接:http://play.golang.org/p/oVoPx5Nf84

英文:

I'm writing a library and I want to return an array (or write to an array) of an unspecific type to the caller. The type can vary, depending on who calls - I can, however, create as many objects of said type from within my function. One way would be that the caller creates an array and the callee fills that - however, there is no way of telling how long this array is going to be. (Is there a way that the callee makes the caller's array bigger? Remember, the callee only sees x interface{}...)

The other way which I chose because I don't see how above is possible, is that the caller gives me the pointer of his specific type and I redirect it to the array of objects which I created.

Below is my solution. My question: why is the array empty after the function call? They are pointing to the same array after my operation, they should be the same. Am I overlooking something? I thought about GC, but it couldn't be that fast, could it?

http://play.golang.org/p/oVoPx5Nf84

package main

import "unsafe"
import "reflect"
import "log"

func main() {
	var x []string
	log.Printf("before: %v, %p", x, x)
	manipulate(&x)
	log.Printf("after: %v, %p", x, x)
}

func manipulate(target interface{}) {
	new := make([]string, 0, 10)
	new = append(new, "Hello", "World")
	log.Printf("new: %v, %p", new, new)
	p := unsafe.Pointer(reflect.ValueOf(target).Pointer())
	ptr := unsafe.Pointer(reflect.ValueOf(new).Pointer())
	*(*unsafe.Pointer)(p) = ptr
}

答案1

得分: 6

首先,不安全的操作通常是一个不好的主意。反射也是如此,但不安全操作至少更糟糕一个数量级。

以下是使用纯反射的示例(http://play.golang.org/p/jTJ6Mhg8q9):

package main

import (
    "log"
    "reflect"
)

func main() {
    var x []string
    log.Printf("before: %v, %p", x, x)
    manipulate(&x)
    log.Printf("after: %v, %p", x, x)
}

func manipulate(target interface{}) {
    t := reflect.Indirect(reflect.ValueOf(target))
    t.Set(reflect.Append(t, reflect.ValueOf("Hello"), reflect.ValueOf("World")))
}

那么,为什么你的不安全的方式没有起作用呢?不安全操作非常棘手,有时需要理解内部工作原理。首先,你有一些误解:

  1. 你正在使用数组:实际上不是,你正在使用切片。切片是数组的一种视图。它们内部包含指向数据的指针、长度和容量。它们在内部是结构体,而不是纯指针。

  2. Pointer 方法只在参数是指针时返回指针:实际上,对于许多类型(如切片),它可以返回一个值。
    来自 http://golang.org/pkg/reflect/#Value.Pointer 的说明:
    > 如果 v 的类型是切片,返回的指针是指向切片的第一个元素的指针。如果切片是 nil,则返回值为 0。如果切片是空但非 nil,则返回值为非零。

  3. 数组是指针:在 Go 中,数组实际上是值。这意味着当它们被传递给其他函数或赋值时,它们会被复制。这也意味着 .Pointer 方法不起作用。

你正在将一个数组的指针赋给一个切片类型。幸运的是,切片在内部使用的实现方式是先存储数据指针,所以你实际上是在设置切片使用的内部数组指针。我必须强调这实际上是纯粹的偶然。即使如此,你没有设置切片的长度和容量,所以它仍然打印出零个元素。

不安全操作让你可以在非常底层进行操作,实际结果并没有明确定义。最好远离它,除非你真的知道自己在做什么。即使如此,要注意事物可能会发生变化,今天有效的方法可能在下一个 Go 版本或其他实现中不再有效。

英文:

First of all, unsafe is usually a bad idea. So is reflection, but unsafe is at least an order of magnitude worse.

Here is your example using pure reflection (http://play.golang.org/p/jTJ6Mhg8q9):

package main

import (
	"log"
	"reflect"
)

func main() {
	var x []string
	log.Printf("before: %v, %p", x, x)
	manipulate(&x)
	log.Printf("after: %v, %p", x, x)
}

func manipulate(target interface{}) {
	t := reflect.Indirect(reflect.ValueOf(target))
	t.Set(reflect.Append(t, reflect.ValueOf("Hello"), reflect.ValueOf("World")))
}

So, why didn't your unsafe way work? Unsafe is extremely tricky and at times requires understanding the internals. First, some misconceptions you have:

  1. You are using arrays: you are not, you are using slices. Slices are a view of an array. They contain within them a pointer to the data, a length, and a capacity. They are stucts internally and not pure pointers.

  2. Pointer returns the pointer only if it is a pointer: it can actually return a value for many types like a slice.
    From http://golang.org/pkg/reflect/#Value.Pointer:
    > If v's Kind is Slice, the returned pointer is to the first element of the slice. If the slice is nil the returned value is 0. If the slice is empty but non-nil the return value is non-zero.

  3. Arrays are pointers: in Go, arrays are actually values. That means they are copied when passed to other functions or assigned. It also means the .Pointer method wouldn't work.

You are assigning a pointer to an array to a slice type. By luck, the implementation of slices used internally has the data pointer first so you are actually setting the internal array pointer used by the slice. I must stress that is is effectively pure accident. Even still, you are not setting the length and capacity of the slice so it still prints zero elements.

Unsafe lets you do things at such a low level that the actual results aren't really defined. It is best to stay away from it unless you really know what you are doing. Even then, be aware that things can can change and what works today may not work in the next version of Go or another implementation.

huangapple
  • 本文由 发表于 2015年4月12日 10:20:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/29585242.html
匿名

发表评论

匿名网友

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

确定