当堆栈扩展并在不安全中遇到无效地址时,如何防止 Go 中的恐慌。指针?

How to prevent panic in Go while stack expands and encounters invalid address in unsafe.Pointer?

提问人:Đorđe Milanović 提问时间:11/17/2023 更新时间:11/17/2023 访问量:37

问:

我正在为我的自定义编程语言开发自己的解释器,它有几种类型(整数、字符串、数组、函数......因此,我陷入了如何有效地表示这些类型的困境。

我的第一个选择是创建通用接口:

type Obj interface {
    Type() Type
    Clone() Obj
    Equals(other Obj) bool
    fmt.Stringer
}

并实现表示每种类型的多个结构:

type Int int
func (integer Int) Type() types.Type { return types.TypeInt }
func (integer Int) Clone() types.Obj { return integer }
func (integer Int) Equals(other types. Obj) bool { ... }

...

type Bool bool
func (boolean Bool) Type() types.Type { return types.TypeBool }
func (boolean Bool) Clone() types.Obj { return boolean }
func (boolean Bool) String() string   { return strconv.FormatBool(bool(Boolean)) }

...

type Array struct {
    Slice []types. Obj
}
func (array *Array) Type() types.Type { return types.TypeArray }
func (array *Array) Clone() types.Obj { ... }
func (array *Array) Equals(other types. Obj) bool { ... }

因此,解释语言中的变量实例将存储为 Interface 的实例。

一切正常,但这种方法很慢。缓慢的原因是动态转换(从接口到具体的结构类型)和大量的堆分配。

然后我想到了标记工会的想法。但是,Golang 没有工会,所以我需要模仿它们。我创建了一个带有标签和字节数组的结构:

type Tag int

type Object struct {
    Tag  Tag
    Data [MAX_SIZE]byte
}

因此,在检查标签后,我会将数组中的数据重新解释为具体类型:

func As[T any](obj Obj) T {
    ptr := (*T)unsafe.Pointer(&obj.Data[0])
    return *ptr
}

我重新实现了所有内容以使用这种类型的系统。但事情开始变得奇怪了。后来我认识到,为了存储某些数据类型(如字符串和数组),需要指针,在这种表示中,它们将被视为该字节数组中的任何其他数字数据,因此 GC 将删除这些对象并可能在同一位置分配一些新对象。

因此,为了“提示”GC不要这样做,我做了一些研究,发现GC关心并会检查指针是否有效以及行为是否正确。unsafe.Pointer

所以我把我模拟的联合结构改为:

type Tag int

type Object struct {
    Tag  Tag
    Data [MaxTypeSize / unsafe.Sizeof(unsafe.Pointer(nil))]unsafe.Pointer
}

有时一切正常,但有时却不正常。程序惊慌失措,给了我

runtime: bad pointer in frame banek/interpreter.(*interpreter).evalBinaryOp at 0xc000292890: 0x1
fatal error: invalid pointer found on stack

我跟踪了堆栈跟踪,发现它因扩展堆栈和执行指针重新分配的功能而惊慌失措。在那一刻,堆栈扩展,我的结构体的某些实例在数组中包含非指针数据(可能只是一个整数)。unsafe.Pointer

所以我的问题是:有没有办法阻止函数调整指针,但 GC 仍然继续检测它们(所以不能使用 )。这将是安全的,因为我没有指向堆栈值的指针,也不需要调整。uintptr

或者,如果这是不可能的,我是否可以以其他方式模拟并集,但无需手动引用计数和固定对象。

go 垃圾回收 unsafe-pointers

评论

2赞 JimB 11/17/2023
如果需要访问堆栈外部的指针,则需要这些堆分配。你正在颠覆转义分析,这就是 Go 确保指针在需要时保持活动状态的方式。我不确定还能怎么做,但我想运行时包中有一些线索需要做一些类似的低级数据管理。
0赞 Đorđe Milanović 11/17/2023
@JimB 我不需要为每个对象分配堆,因为值(整数、布尔值、字符串描述符等)将直接存储在该字节数组中。这就是我创建(模仿)工会的原因。但问题在于表示指针的数据(例如字符串描述符中的指针),并且 GC 不知道该指针。
0赞 JimB 11/18/2023
啊,好吧,你真的不能那样做。指针总是会被视为指针,你不能弄乱指针位本身,因为 GC 会将一些额外的位用于它自己的标签(尽管我不记得它实际使用的细节)
0赞 JimB 11/18/2023
对于与运行时实现如此紧密交织的东西,邮件列表可能是一个更合适的论坛。更熟悉内部结构的人可能会有更多想法

答: 暂无答案