dongzanghui4624 2018-07-05 09:36
浏览 112
已采纳

如何在golang中从不安全的数组创建数组或切片?

A pointer to an array, let's say:

p := uintptr(unsafe.Pointer(&array))
size := 5

I can't access to the variable array, the code above is used to make it more clear.

Also, I know the size of the array, but the size is not constant, it changes according to the runtime.

Now, I want to initialize a slice or an array with the known pointer, size and of course the data type.

I come up with the following code:

data := make([]byte, size)
stepSize := unsafe.Sizeof(data[0])
for i := 0; i < size; i++ {
    data[i] = *(*byte)(unsafe.Pointer(p))
    p += stepSize
}
fmt.println(data)

but this method does memory copy, which might be inefficient, is there anyway without doing the copy?

P.S. I also tried the following two methods,

// method 1
data := *(*[]byte)(unsafe.Pointer(p))
// method 2
data := *(*[size]byte)(unsafe.Pointer(p))

but it will fail at runtime and I know its reason now.

  • 写回答

1条回答 默认 最新

  • dsc56927 2018-07-05 09:54
    关注

    Foreword:

    You should know: if you get the pointer as a value of uintptr type, that does not prevent the original array to get garbage collected (an uintptr value does not count as a reference). So be careful when using such value, there is no guarantee it will point to a valid value / memory area.

    Quoting from package unsafe.Pointer:

    A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.

    General advice: stay away from package unsafe as much as possible. Stay inside Go's type safety.


    Declare a variable of slice type, and use unsafe conversions to obtain its reflect.SliceHeader descriptor.

    Then you can modify its fields, using the pointer as the SliceHeader.Data value, and the size as SliceHeader.Len and SliceHeader.Cap.

    Once you're done with this, the slice variable will point to the same array as your initial pointer.

    arr := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    size := len(arr)
    p := uintptr(unsafe.Pointer(&arr))
    
    var data []byte
    
    sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
    sh.Data = p
    sh.Len = size
    sh.Cap = size
    
    fmt.Println(data)
    
    runtime.KeepAlive(arr)
    

    Output (try it on the Go Playground):

    [0 1 2 3 4 5 6 7 8 9]
    

    Note that I used runtime.KeepAlive(). This is because after taking the address of arr and getting its length, we don't refer to arr anymore (p being uintptr does not count as a reference), and an aggressive GC might–rightfully–erase arr before we get to the point to print data (pointing to arr). Placing a runtime.KeepAlive() to the end of main() will ensure that arr will not be garbage collected before this call. For details, see In Go, when will a variable become unreachable? You do not need to call runtime.KeepAlive() in your code if the supplier of the pointer ensures it will not be garbage collected.

    Alternatively you can create a reflect.SliceHeader with a composite literal, and use unsafe conversions to obtain a slice from it, like this:

    sh := &reflect.SliceHeader{
        Data: p,
        Len:  size,
        Cap:  size,
    }
    
    data := *(*[]byte)(unsafe.Pointer(sh))
    
    fmt.Println(data)
    
    runtime.KeepAlive(arr)
    

    Output will be the same. Try this one on the Go Playground.

    This possibility / use case is documented at unsafe.Pointer, with the caveats and warnings:

    (6) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer.

    As in the previous case, the reflect data structures SliceHeader and StringHeader declare the field Data as a uintptr to keep callers from changing the result to an arbitrary type without first importing "unsafe". However, this means that SliceHeader and StringHeader are only valid when interpreting the content of an actual slice or string value.

    var s string
    hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
    hdr.Data = uintptr(unsafe.Pointer(p))              // case 6 (this case)
    hdr.Len = n
    

    In this usage hdr.Data is really an alternate way to refer to the underlying pointer in the slice header, not a uintptr variable itself.

    In general, reflect.SliceHeader and reflect.StringHeader should be used only as *reflect.SliceHeader and *reflect.StringHeader pointing at actual slices or strings, never as plain structs. A program should not declare or allocate variables of these struct types.

    // INVALID: a directly-declared header will not hold Data as a reference.
    var hdr reflect.StringHeader
    hdr.Data = uintptr(unsafe.Pointer(p))
    hdr.Len = n
    s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 DSP28335AD采集实验
  • ¥15 在winods上用C# 怎么编写CAN通信在linux运行
  • ¥15 关于螺旋焊缝的图像处理
  • ¥15 qt 散点图自定义图形
  • ¥100 将USDZ文件转化为带颜色的OBJ文件
  • ¥15 对象代号: , 表单: 不存在!
  • ¥15 WebSocket的问题
  • ¥15 centos上启动kylin后网页报错404
  • ¥20 使用hackrf进行信号收发时接收到的信号幅度太小
  • ¥15 WebSocket的问题