我有一个Go对象,我希望它在内存中的地址保持不变。在C#中,可以将对象的位置固定在内存中。在Go中有没有办法做到这一点?
70gysomp1#
一个你保持引用的对象不会移动。没有句柄或间接寻址,你得到的地址是永久的。从the documentation:请注意,与C不同,返回局部变量的地址是完全可以的;与变量相关的存储在函数返回后仍然存在当你设置一个变量时,你可以使用&操作符读取这个地址,然后你可以传递它。
&
yzxexxkh2#
不,但这并不重要,除非你想做一些不寻常的事情。值得注意的是,公认的答案有一部分是不正确的。不能保证对象不会被移动--无论是在堆栈上还是在Go堆上--但是只要你不使用unsafe,这对你来说就没关系,因为Go运行时会在对象被移动的情况下透明地更新你的指针。如果OTOH你使用unsafe来获取一个uintptr,调用原始系统调用,执行CGO调用,或者以其他方式暴露地址(例如oldAddr := fmt.Sprintf("%p", &foo)),等等,你应该知道地址可以改变,编译器和运行时都不会神奇地为你打补丁。虽然目前标准的Go编译器只移动堆栈上的对象(例如,当一个goroutine堆栈需要调整大小时),但Go语言规范中没有任何内容阻止不同的实现在Go堆上移动对象。虽然(yet)没有明确支持将对象固定在堆栈或Go堆中,有一个推荐的解决方法:在Go堆之外手动分配内存(例如通过mmap)使用finalizer在所有对它的引用都被删除后自动释放该分配。这种方法的好处是,在Go堆之外手动分配的内存将永远不会被释放。被Go运行时移动,所以它的地址永远不会改变,但是当它不再需要时,它仍然会自动释放,所以它不会泄漏。
unsafe
uintptr
oldAddr := fmt.Sprintf("%p", &foo)
mmap
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
3条答案
按热度按时间70gysomp1#
一个你保持引用的对象不会移动。没有句柄或间接寻址,你得到的地址是永久的。
从the documentation:
请注意,与C不同,返回局部变量的地址是完全可以的;与变量相关的存储在函数返回后仍然存在
当你设置一个变量时,你可以使用
&
操作符读取这个地址,然后你可以传递它。yzxexxkh2#
不,但这并不重要,除非你想做一些不寻常的事情。
值得注意的是,公认的答案有一部分是不正确的。
不能保证对象不会被移动--无论是在堆栈上还是在Go堆上--但是只要你不使用
unsafe
,这对你来说就没关系,因为Go运行时会在对象被移动的情况下透明地更新你的指针。如果OTOH你使用
unsafe
来获取一个uintptr
,调用原始系统调用,执行CGO调用,或者以其他方式暴露地址(例如oldAddr := fmt.Sprintf("%p", &foo)
),等等,你应该知道地址可以改变,编译器和运行时都不会神奇地为你打补丁。虽然目前标准的Go编译器只移动堆栈上的对象(例如,当一个goroutine堆栈需要调整大小时),但Go语言规范中没有任何内容阻止不同的实现在Go堆上移动对象。
虽然(yet)没有明确支持将对象固定在堆栈或Go堆中,有一个推荐的解决方法:在Go堆之外手动分配内存(例如通过
mmap
)使用finalizer在所有对它的引用都被删除后自动释放该分配。这种方法的好处是,在Go堆之外手动分配的内存将永远不会被释放。被Go运行时移动,所以它的地址永远不会改变,但是当它不再需要时,它仍然会自动释放,所以它不会泄漏。nhhxz33t3#
tldr:使用runtime.Pinner
在一些特别情况下,是可以的,而且是有需要的。
例如,如果你想尽可能地避免go的gc开销,你可以选择自己实现一个buffer或类似于arena的结构,基于unix.mmap和unix.mummap实现,或者使用cgo。
我现在工作的时候就处于这样一种情况,我正在使用其中一种数据结构来修改解析器树创建新节点的方式,修改的关键是使创建新节点的内存来自c(也称为cgo)而不是go。
字符串
但是这个构造函数会遇到问题,当你用cgocheck=2运行检查时,你会遇到类似下面的问题。
enter image description here
这是因为字符串也是指针,而指针指向non-go内存,因为gc不会扫描该内存,这意味着gc不知道字符串被引用,因此它们可能会被垃圾收集。
型
https://github.com/golang/go/blob/master/src/runtime/cgocheck.go#L54
想要这些go对象不被回收,就需要进行pin,而go现在提供了这种机制,相应地,cgocheck检测逻辑也有了相应的地方
这是我第一次回答问题,英语不是我的第一语言,所以如果有任何尴尬的短语或事实错误,请让我知道,谢谢!
更多信息:https://github.com/golang/go/issues/12416