求大佬们看一下,通过指针强行转换类型,运行时会不会被教育.

2023-02-02 14:34:54 +08:00
 whitehack

有个非常大的结构而且是数组. 走 grpc 返回结果要一个一个赋值

想直接通过指针转换过来, 看起来可行 但是怕运行时被教育

https://go.dev/play/p/v5vJ53sXiEs

// You can edit this code!
// Click here and start typing.
package main

import (
	"fmt"
	"sync"
	"unsafe"
)

// NoUnkeyedLiterals can be embedded in a struct to prevent unkeyed literals.
type NoUnkeyedLiterals struct{}

// DoNotCompare can be embedded in a struct to prevent comparability.
type DoNotCompare [0]func()

// DoNotCopy can be embedded in a struct to help prevent shallow copies.
// This does not rely on a Go language feature, but rather a special case
// within the vet checker.
//
// See https://golang.org/issues/8005.
type DoNotCopy [0]sync.Mutex

// Requirements:
//   - The type M must implement protoreflect.ProtoMessage.
//   - The address of m must not be nil.
//   - The address of m and the address of m.state must be equal,
//     even though they are different Go types.
type MessageState struct {
	NoUnkeyedLiterals
	DoNotCompare
	DoNotCopy

	// atomicMessageInfo *MessageInfo
}

type (
	UnknownFields  = unknownFieldsA // TODO: switch to unknownFieldsB
	unknownFieldsA = []byte
)

type JuDianUpdateTeamReq struct {
	state         MessageState
	sizeCache     int32
	unknownFields UnknownFields

	Id      string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
	CurHp   uint64 `protobuf:"varint,2,opt,name=curHp,proto3" json:"curHp,omitempty"`
	TotalHp uint64 `protobuf:"varint,3,opt,name=totalHp,proto3" json:"totalHp,omitempty"`
}

type TeamData struct {
	Id      string `json:"id,omitempty"`
	CurHp   uint64 `json:"curHp,omitempty"`
	TotalHp uint64 `json:"totalHp,omitempty"`
}

func main() {
	juDianUpdateTeamReq := &JuDianUpdateTeamReq{
		Id:      "team1",
		CurHp:   100,
		TotalHp: 200,
	}
	teamData := (*TeamData)(unsafe.Pointer(&juDianUpdateTeamReq.Id))
	fmt.Println("teamData", teamData)
}

873 次点击
所在节点    Go 编程语言
16 条回复
yicixin
2023-02-03 17:18:30 +08:00
就这个例子来讲,直接指针转换可以省去内存分配和拷贝,写法也更方便,但是要注意`juDianUpdateTeamReq`和`teamData `指向同一片内存了,它们是会相互影响的,小心不要出现内存竞争。
whitehack
2023-02-03 18:54:34 +08:00
@yicixin #1 感谢回复. `juDianUpdateTeamReq`这个是 grpc 返回的,所以不用担心内存竟争

teamData 是否有被垃圾回收的风险?

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

这边有个详细一点的例子.
leonshaw
2023-02-04 10:57:28 +08:00
肯定是未定义行为,但是实践上应该没太大问题。
lysS
2023-02-06 15:17:15 +08:00
你既然有内存中的 juDianUpdateTeamReq 了,可以对它任意读写,转换成 TeamData 有啥意义呢?
lysS
2023-02-06 15:20:37 +08:00
不知大直接这样定义行不行 https://go.dev/play/p/phb2AxTdvyN
whitehack
2023-02-06 16:21:38 +08:00
@lysS #4 因为要通过另一套结构返回给别的接口. 那个接口定义的 TeamData


@lysS #5 这样不满足我的需求. grpc 的结构是通过 proto 自动生成的. teamdata 结构也是自动生成的 是两套系统
xuyang2
2023-02-08 11:22:45 +08:00
对内存细节没理解清楚的话,不要乱用 unsafe
xuyang2
2023-02-08 11:25:21 +08:00
为什么不用最简单的写法呢

```
teamData := TeamData{
Id: juDianUpdateTeamReq.Id,
// CurHp: 0,
// TotalHp: 0,
}
fmt.Println("teamData", &teamData)
```
xuyang2
2023-02-08 11:25:43 +08:00
string 本身就是个 fat pointer
whitehack
2023-02-08 13:52:27 +08:00
@xuyang2 #8 是这样的,这个只是示例结构.实际结构 都是数组,而且字段非常多.

所以用 unsafe 是为了减少内存分配.并且提高性能.
xuyang2
2023-02-08 15:40:19 +08:00
- 过早优化是万恶之源
- 不要滥用指针 包括指针数组 减轻 GC 的负担
- 你计算过这个 struct 的字节大小吗
whitehack
2023-02-08 16:21:03 +08:00
@xuyang2 #11 > 过早优化是万恶之源

大佬说的太对了. 只是一个一个字段的赋值也是很麻

指针数组倒是没有用,.另外这 proto-go-gen 生成的结构都是指针,在 issue 查了一下,说是就是这样设计的.


一个 struct 大概 10-30 个左右的字段吧
xuyang2
2023-02-08 16:25:30 +08:00
一个 struct 大概 10-30 个左右的字段
常见的 数字 string slice map 类型,都很小
这样一个 struct 也就几十 /上百字节

slice []MyStruct 本身也是胖指针,没记个字节
数组 [n]MyStruct 的大小是 n * MyStruct 大小

这些在栈上传递都很快的
xuyang2
2023-02-08 16:33:23 +08:00
自己用 unsafe 处理指针,全靠自己记住内存布局,编译器帮不上忙

如果源 struct 后面有调整,旧代码也不会报错,可能会得到莫名其妙的结果
而且这种代码搜索和修改也很麻烦
whitehack
2023-02-08 17:46:25 +08:00
@xuyang2 #14

写了个结构体大小的比较.计算不对直接 panic, 后面的事后面再说 哈哈
xuyang2
2023-02-08 18:27:33 +08:00
不能只比较大小的

瞎玩 unsafe 是作死行为

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/912580

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX