你能用Go把一个对象“钉”在内存中吗?

ufj5ltwl  于 2023-11-14  发布在  Go
关注(0)|答案(3)|浏览(115)

我有一个Go对象,我希望它在内存中的地址保持不变。在C#中,可以将对象的位置固定在内存中。在Go中有没有办法做到这一点?

70gysomp

70gysomp1#

一个你保持引用的对象不会移动。没有句柄或间接寻址,你得到的地址是永久的。
the documentation
请注意,与C不同,返回局部变量的地址是完全可以的;与变量相关的存储在函数返回后仍然存在
当你设置一个变量时,你可以使用&操作符读取这个地址,然后你可以传递它。

yzxexxkh

yzxexxkh2#

不,但这并不重要,除非你想做一些不寻常的事情。
值得注意的是,公认的答案有一部分是不正确的。
不能保证对象不会被移动--无论是在堆栈上还是在Go堆上--但是只要你不使用unsafe,这对你来说就没关系,因为Go运行时会在对象被移动的情况下透明地更新你的指针。
如果OTOH你使用unsafe来获取一个uintptr,调用原始系统调用,执行CGO调用,或者以其他方式暴露地址(例如oldAddr := fmt.Sprintf("%p", &foo)),等等,你应该知道地址可以改变,编译器和运行时都不会神奇地为你打补丁。
虽然目前标准的Go编译器只移动堆栈上的对象(例如,当一个goroutine堆栈需要调整大小时),但Go语言规范中没有任何内容阻止不同的实现在Go堆上移动对象。
虽然(yet)没有明确支持将对象固定在堆栈或Go堆中,有一个推荐的解决方法:在Go堆之外手动分配内存(例如通过mmap)使用finalizer在所有对它的引用都被删除后自动释放该分配。这种方法的好处是,在Go堆之外手动分配的内存将永远不会被释放。被Go运行时移动,所以它的地址永远不会改变,但是当它不再需要时,它仍然会自动释放,所以它不会泄漏。

nhhxz33t

nhhxz33t3#

tldr:使用runtime.Pinner

在一些特别情况下,是可以的,而且是有需要的。
例如,如果你想尽可能地避免go的gc开销,你可以选择自己实现一个buffer或类似于arena的结构,基于unix.mmap和unix.mummap实现,或者使用cgo。
我现在工作的时候就处于这样一种情况,我正在使用其中一种数据结构来修改解析器树创建新节点的方式,修改的关键是使创建新节点的内存来自c(也称为cgo)而不是go。

type CStr struct {
    o string
    c string
}

func NewCStr(str string, lower int64, buf *buffer.Buffer) *CStr {
    // cs := new(CStr)
    cs := buffer.Alloc[CStr](buf)
    
    cs.o = str
    if lower == 0 {
        cs.c = cs.o
        return cs
    }
    cs.c = strings.ToLower(cs.o)
    return cs
}

字符串
但是这个构造函数会遇到问题,当你用cgocheck=2运行检查时,你会遇到类似下面的问题。
enter image description here
这是因为字符串也是指针,而指针指向non-go内存,因为gc不会扫描该内存,这意味着gc不知道字符串被引用,因此它们可能会被垃圾收集。

...
    // If the object is pinned, it's safe to store it in C memory. The GC
    // ensures it will not be moved or freed.
    if isPinned(src) {
        return
    }
...


https://github.com/golang/go/blob/master/src/runtime/cgocheck.go#L54
想要这些go对象不被回收,就需要进行pin,而go现在提供了这种机制,相应地,cgocheck检测逻辑也有了相应的地方
这是我第一次回答问题,英语不是我的第一语言,所以如果有任何尴尬的短语或事实错误,请让我知道,谢谢!
更多信息:https://github.com/golang/go/issues/12416

相关问题