使用reflect和unsafe包复制切片

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

Copy slice with reflect and unsafe package

问题

我理解你的问题。你正在尝试使用unsafe包来进行一些教育目的的操作,但是你意识到这样做是不安全的。

你的想法是复制一个结构体中的切片字段,使用反射包和unsafe.Pointer进行复制,然后修改并替换原始切片。看起来新的切片被创建了,并且具有正确的容量/长度,并且包含了GoodForSale类型的实例。但是该实例的所有字段(Name、Price和Qnty)的值都是错误的。所以我猜测你在处理指针和获取垃圾数据方面做错了什么。

你想知道如何使这个想法正确工作。

英文:

I understand that usage of unsafe package of golang is unsafe, but I'm doing it only for education purposes.

The idea is to copy a slice field of structure, copy it with reflect package and unsafe.pointer, modify and replace the original slice with new one.

And it looks like new slice is created and has correct capacity/length, and it contains an instance of GoodForSale type. But all fields of that instance (Name, Price and Qnty) have wrong values. So I suppose that I'm doing something wrong with pointers and getting garbade data:

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type GoodForSale struct {
	Name string
	Price int
	Qnty int
}

type Lead struct {
	ID int
	Name string
	ContactName string
	Budget int
	Items []GoodForSale
	DateTime string
	Status int
	Win bool
}

func main()  {
	lead := &Lead{
		ID:          41,
		Name:        "Phone",
		ContactName: "John Smith",
		Budget:      30000,
		Items:       []GoodForSale{
			{
				Name:  "Iphone 6",
				Price: 100,
				Qnty:  3,
			},
		},
		DateTime:    "12.08.2020 11:23:10",
		Status: 150,
		Win:         false,
	}


	//Change Items
	pt1 := unsafe.Pointer(&lead.Items)
	copyItems := &reflect.SliceHeader{
		Data: uintptr(pt1),
		Cap: cap(lead.Items),
		Len: len(lead.Items),
	}


	items := *(*[]GoodForSale)(unsafe.Pointer(copyItems))
	fmt.Println(items[0].Name)

}

It looks like I'm missing something about how pointers work here. But how can I make this idea work correct?

Here is a go playground url: https://play.golang.org/p/SKtJWe9RVEU

答案1

得分: 3

问题在这里:

pt1 := unsafe.Pointer(&lead.Items) // 指向切片的指针,也就是“切片头”
copyItems := &reflect.SliceHeader{
        Data: uintptr(pt1), // 尝试将其用作指向实际数据的指针。

"Data" 需要一个指向实际数据开始位置的指针,但你给的是指向切片本身的指针。

这是获取指向切片数据的正确方法:

pt1 := unsafe.Pointer(&lead.Items[0]) // 指向切片引用的第一个元素的指针

请注意,如果 len(lead.Items) == 0,这将引发 panic。在这种情况下,你应该使用 unsafe.Pointer(nil)

你还可以通过将原始切片强制转换为 reflect.SliceHeader 来获取数据指针(以及长度和容量),如下所示:

copyHeader := *(*reflect.SliceHeader)(unsafe.Pointer(&lead.Items))

items := *(*[]GoodForSale)(unsafe.Pointer(&copyHeader))

但在这一点上,我们实际上只是制造了一个复杂和 "不安全" 的版本,相当于:

items := lead.Items
英文:

The problem is here:

pt1 := unsafe.Pointer(&lead.Items) // pointer the slice, a.k.a "slice header"
copyItems := &reflect.SliceHeader{
        Data: uintptr(pt1), // trying to use it as pointer to the actual data.

"Data" needs a pointer to where the actual data starts, but you're giving a pointer to the slice itself.

This is the correct way to get a pointer to the slice's data:

pt1 := unsafe.Pointer(&lead.Items[0]) // pointer to the first element referenced by the slice

Note that this will panic if len(lead.Items) == 0. In that case, you should use unsafe.Pointer(nil).

You could also get the data pointer (along with len and cap) from the original slice by casting it as a reflect.SliceHeader like this:

copyHeader := *(*reflect.SliceHeader)(unsafe.Pointer(&lead.Items))

items := *(*[]GoodForSale)(unsafe.Pointer(&copyHeader))

but at this point we've essentially just made a convoluted and "unsafe" version of this:

items := lead.Items

答案2

得分: 1

根据 Hymns For Disco 的输入,通过为指针提供切片元素而不是整个切片来修改您的代码。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type GoodForSale struct {
	Name  string
	Price int
	Qnty  int
}

type Lead struct {
	ID          int
	Name        string
	ContactName string
	Budget      int
	Items       []GoodForSale
	DateTime    string
	Status      int
	Win         bool
}

func main() {
	lead := &Lead{
		ID:          41,
		Name:        "Phone",
		ContactName: "John Smith",
		Budget:      30000,
		Items: []GoodForSale{
			{
				Name:  "Iphone 6",
				Price: 100,
				Qnty:  3,
			},
		},
		DateTime: "12.08.2020 11:23:10",
		Status:   150,
		Win:      false,
	}

	pt1 := unsafe.Pointer(&lead.Items[0])

	copyHeader := *(*reflect.SliceHeader)(unsafe.Pointer(&lead.Items[0]))

	items := *(*[]GoodForSale)(unsafe.Pointer(&copyHeader))

	fmt.Println(pt1)
	fmt.Println(items[0].Name)
}

输出:

0xc00000c080
Iphone 6
英文:

Based on the inputs from Hymns For Disco modifying your code by providing the element of slice instead of whole slice to the pointer.

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type GoodForSale struct {
	Name  string
	Price int
	Qnty  int
}

type Lead struct {
	ID          int
	Name        string
	ContactName string
	Budget      int
	Items       []GoodForSale
	DateTime    string
	Status      int
	Win         bool
}

func main() {
	lead := &Lead{
		ID:          41,
		Name:        "Phone",
		ContactName: "John Smith",
		Budget:      30000,
		Items: []GoodForSale{
			{
				Name:  "Iphone 6",
				Price: 100,
				Qnty:  3,
			},
		},
		DateTime: "12.08.2020 11:23:10",
		Status:   150,
		Win:      false,
	}

	
	pt1 := unsafe.Pointer(&lead.Items[0])

	copyHeader := *(*reflect.SliceHeader)(unsafe.Pointer(&lead.Items))

	items := *(*[]GoodForSale)(unsafe.Pointer(&copyHeader))

	//items := *(*[]GoodForSale)(unsafe.Pointer(copyItems))
	fmt.Println(pt1)
	fmt.Println(items[0].Name)

}

Output:

0xc00000c080
Iphone 6

huangapple
  • 本文由 发表于 2021年7月15日 12:51:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/68388088.html
匿名

发表评论

匿名网友

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

确定