在 CGO 分配的内存上过早调用终结器

问题描述 投票:0回答:1

我有一个项目,需要使用 C 库加载大图像,并且我想避免将数据复制到 Go 托管内存中。为此,我使用

godata := unsafe.Slice((*uint8)(cdata), size)
创建一个指向非托管内存的切片。

然后我调用

runtime.SetFinalizer(&godata, free)
来允许在切片被 GC 时释放内存。

我的问题是在最后一次使用

godata
之前调用终结器,导致段错误。

package main

/*
#include <stdlib.h>

unsigned char* allocate(int size) {
  return (unsigned char*)malloc(size);
}

void free_allocated(unsigned char *c) {
  free(c);
}
*/
import "C"
import (
    "runtime"
    "time"
    "unsafe"
)

type dummy struct {
    Data []uint8
}

func allocate(size int) *dummy {
    cdata := C.allocate((C.int)(size))
    godata := unsafe.Slice((*uint8)(cdata), size)
    runtime.SetFinalizer(&godata, func(data *[]uint8) {
        println("Freeing")
        C.free_allocated(cdata)
    })
    // The issue does not occur without wrapping godata in a Dummy struct
    return &dummy{Data: godata}
}

func main() {
    println("Start")

    dummy := allocate(1_000_000_000)
    dummy.Data[100_000_000] = 1

    println("Allocated")

    println("GC")
    runtime.GC()

    // Wait to let the system reclaim the memory properly
    println("Wainting 10 seconds")
    <-time.After(10 * time.Second)

    // dummy.Data is already freed at this point, causing a fault
    println("Value is: ", dummy.Data[100_000_000])
}

对我来说,“Freeing”是在“GC”之后打印的。

我的问题是如何正确使用

SetFinalizer
,这样就不会发生这个问题。

go segmentation-fault cgo
1个回答
0
投票

我发现了一个似乎有效的技巧,但可能并不总是有效,所以我不会将此答案标记为已接受。不要在生产中使用此代码!!

func allocate(size int) *dummy {
    cdata := C.allocate((C.int)(size))
    goslice := unsafe.Slice((*uint8)(cdata), size)

    // An empty struct has zero bytes, so only the memory for the pointer should be allocated.
    // 'ptr' is a pointer into go managed memory, so SetFinalizer won't complain
    ptr := new(struct{})
    runtime.SetFinalizer(ptr, func(_ *struct{}) {
        println("Freeing")
        C.free_allocated(cdata)
    })
    // Changing the address of the pointer in this way 'tricks' the compiler.
    // See https://github.com/golang/go/issues/58625#issuecomment-1642687911
    // This effectively makes the new address managed, as I understand it
    *(*uintptr)(unsafe.Pointer(&ptr)) = uintptr(unsafe.Pointer(cdata))

    return &dummy{Data: goslice}
}
© www.soinside.com 2019 - 2024. All rights reserved.