使用反射和不安全包复制切片

Copy slice with reflect and unsafe package

我知道使用 unsafe golang 包是不安全的,但我这样做只是为了教育目的。

思路是复制一个structure的slice字段,用reflect包复制,unsafe.pointer,修改替换原来的slice。

看起来新切片已创建并且具有正确的 capacity/length,并且它包含 GoodForSale 类型的实例。但是该实例的所有字段(名称、价格和数量)都有错误的值。所以我想我在使用指针和获取 garbade 数据时做错了什么:

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)

}

我似乎遗漏了一些有关指针在这里如何工作的信息。但是我怎样才能使这个想法正确呢?

这是一个围棋游戏场url:https://play.golang.org/p/SKtJWe9RVEU

问题出在这里:

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.

“数据”需要指向实际数据开始位置的指针,但您给出的是指向切片本身的指针。

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

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

请注意,如果 len(lead.Items) == 0,这将导致恐慌。在这种情况下,您应该使用 unsafe.Pointer(nil).

您还可以通过将其转换为 reflect.SliceHeader 从原始切片中获取数据指针(连同 len 和 cap),如下所示:

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

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

但在这一点上,我们基本上只是制作了一个复杂且“不安全”的版本:

items := lead.Items

根据 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))

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

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

}

输出:

0xc00000c080
Iphone 6