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 在若依框架下实现人脸识别
  • ¥15 网络科学导论,网络控制
  • ¥100 安卓tv程序连接SQLSERVER2008问题
  • ¥15 利用Sentinel-2和Landsat8做一个水库的长时序NDVI的对比,为什么Snetinel-2计算的结果最小值特别小,而Lansat8就很平均
  • ¥15 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同