使用`unsafe.Pointer`直接将一个结构体`point`转换为另一个结构体是安全的吗?

huangapple go评论86阅读模式
英文:

Is it safe to directly convert a struct 'point' to another struct using unsafe.Pointer?

问题

这是一个关于Go语言的代码片段,主要涉及到指针和类型转换。以下是翻译好的内容:

这段代码中使用了指针和类型转换。(*TeamData)(unsafe.Pointer(&team.Id))这一行代码将team.Id的地址转换为*TeamData类型的指针。这种转换是不安全的,因为它绕过了Go语言的类型系统。

testTrans函数中,通过遍历teams切片,将每个team.Id的地址转换为*TeamData类型的指针,并添加到teamDatas切片中。最后,teamDatas切片作为函数的返回值。

关于teams := testTrans()数组的成员是否会被垃圾回收,这取决于具体的上下文。如果teamDatas切片在函数外部被引用,那么其中的元素不会被垃圾回收。如果teamDatas切片只在函数内部使用,并且没有被返回或者被其他地方引用,那么其中的元素可能会被垃圾回收。

需要注意的是,使用不安全的指针转换可能会导致内存安全问题和未定义行为。在使用这种转换之前,应该仔细考虑代码的安全性和可维护性。

完整的示例代码可以在以下链接中找到:https://go.dev/play/p/q3gwp2mERvj

英文:

Is it safe?

> (*TeamData)(unsafe.Pointer(&team.Id))

Example code:


func testTrans() []*TeamData {
	teams := createTeams()
	teamDatas := make([]*TeamData, 0, len(teams))
	for _, team := range teams {
        // is this safe?
		teamDatas = append(teamDatas, (*TeamData)(unsafe.Pointer(&team.Id)))
	}
	return teamDatas
}

// ??
teams := testTrans()

Will the members of the teams := testTrans() array be garbage collected?

There are many structs and many fields returned through grpc and their definitions are the same as the local definitions, so I want to use this more efficient way((*TeamData)(unsafe.Pointer(&team.Id))), but I don't know if there will be any risks.

Full Example:
https://go.dev/play/p/q3gwp2mERvj

答案1

得分: 1

unsafe.Pointer 的文档描述了其支持的用法。特别是:

(1) 将 *T1 转换为 *T2 的指针。

假设 T2 不比 T1 大,并且两者具有相同的内存布局,这种转换允许将一种类型的数据重新解释为另一种类型的数据。

Go 的垃圾回收器识别内部指针,并且只有在没有对该块的引用时才会回收原始分配。因此,在存在对 *TeamData 的引用时,较大的分配(例如你的示例中的 GrpcRetTeam)将被固定。

另一个关键的考虑因素是结构字段的对齐方式。例如:

type Parent struct {
    A uint8
    B uint8
    // 6 字节的填充以对齐 C。
    C uint64
}

type Bad struct {
    B uint8
    // 7 字节的填充以对齐 C。
    C uint64
}

在这种情况下,使用 unsafe 从 Parent 中提取 Bad 将是无效的,因为内存布局不同。

在大多数情况下,最好避免使用 unsafe.Pointer 的技巧,除非需要满足功能或性能要求。通常可以重构代码以最小化分配。

如果必须使用 unsafe 来满足性能要求 -
我建议使用 reflect 包实现一个测试,以确保子结构体的内存对齐/布局是有效的。

英文:

The documentation for unsafe.Pointer describes supported uses. In particular:

> (1) Conversion of a *T1 to Pointer to *T2.
>
> Provided that T2 is no larger than T1 and that the two share an
> equivalent memory layout, this conversion allows reinterpreting data
> of one type as data of another type.

Go's garbage collector recognises interior pointers an will not collect the original allocation until there are no remaining references to that block.
Hence the larger allocation (GrpcRetTeam in your example) will be pinned while references to *TeamData exists.

Another critical consideration is the alignment of the struct fields. Eg:

type Parent struct {
    A uint8
    B uint8
    // 6 bytes of padding to align C.
    C uint64
}

type Bad struct {
    B uint8
    // 7 bytes of padding to align C.
    C uint64
}

In this case it would be invalid to use unsafe to extract Bad from Parent since the memory layout is different.

In most cases it's typically better to avoid unsafe.Pointer tricks unless required to meet functionality or performance requirements. It's often possible to refactor code to minimise allocations instead.

If you must use unsafe to meet performance requirements --
I would recommend implementing a test using the reflect package to ensure the memory alignment/layout is valid for the child struct.

huangapple
  • 本文由 发表于 2023年2月3日 17:37:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75333882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定