Go 1.19.3 unsafe包

使用unsafe包,可以绕过Go语言的类型系统,直接读写内存。Go官方不提倡使用该包,使用该包时应从安全性、兼容性等多方面进行综合考量。
unsafe.Sizeof() 函数用于查看变量所占用的内存大小(多少字节)
unsafe.Offsetof() 函数用于查看结构体中字段相对于结构体起始地址的偏移量
unsafe.Alignof() 函数用于查看变量的内存对齐方式

	type Structure struct {
		BOOL  bool
		INT64 int64
	}

	s := Structure{}

	fmt.Println(unsafe.Sizeof(s))         // 16
	fmt.Println(unsafe.Sizeof(s.BOOL))    // 1
	fmt.Println(unsafe.Sizeof(s.INT64))   // 8
	fmt.Println(unsafe.Offsetof(s.BOOL))  // 0
	fmt.Println(unsafe.Offsetof(s.INT64)) // 8
	fmt.Println(unsafe.Alignof(s))        // 8
	fmt.Println(unsafe.Alignof(s.BOOL))   // 1
	fmt.Println(unsafe.Alignof(s.INT64))  // 8

unsafe.Pointer 非类型安全指针,可以与类型安全指针相互转换,修改内存中的内容
unsafe.Add() 用于unsafe.Pointer的位置偏移运算

	type str struct { // string 内存布局
		data unsafe.Pointer
		len  int
	}
	
	type Student struct {
		Name str
		age  int64
	}
	
	s := Student{}
	sptr := unsafe.Pointer(&s)
	*(*string)(sptr) = "张三"
	*(*int)(unsafe.Pointer(uintptr(sptr) + unsafe.Sizeof(s.Name))) = 18
	*(*int)(unsafe.Add(sptr, unsafe.Sizeof(s.Name))) = 19
	fmt.Println(*(*string)(unsafe.Pointer(&s.Name)))
	fmt.Println(s.age)

应直接在unsafe.Pointer()的括号中使用uintptr强转的变量进行地址运算,以免某些变量被GC。Go语言中栈是动态变化的,一个栈上变量的地址是极有可能发生变化的。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	bs := []byte{'H', 'E', 'L', 'L', 'O'}
	str := bytes2string(bs)
	fmt.Println(str)

	bs[0] = 'h'
	fmt.Println(str)

	bs = string2bytes(str)
	bs[0] = 'H'
	fmt.Println(str)
	fmt.Println(len(bs))
	fmt.Println(cap(bs))
}

func bytes2string(bytes []byte) string {
	return *(*string)(unsafe.Pointer(&bytes))
}

func string2bytes(str string) []byte {
	return *(*[]byte)(unsafe.Pointer(&(struct {
		string
		int
	}{string: str, int: len(str)})))
}

零拷贝,打破了string变量不可修改的规范。byte切片与string共用底层byte数组。修改一方另一方也随之变动。

unsafe.Slice() 用已有的数组创建一个切片,其长度和容量应为数组的长度

    array := []int{1, 2, 3, 4, 5}
	fmt.Println(unsafe.Slice(&array[0], len(array))) // [1 2 3 4 5]

闲扯两句
通过unsafe包,可以修改任意结构体的字段,即使是非导出字段(任意包下的,只要你知道其内存布局),这种行为非常hack,在绕过类型系统的同时,为Go语言带来了一定的自由;但稍有不慎,就可能让你的runtime崩溃。若两个结构体类型内存布局相同,则可以通过unsafe.Pointer无缝转换。在使用unsafe.Pointer强转时,uintptr类型的中间量不应轻易的赋值给其他变量使用,它是一个整数,代表的是地址,但它不会像指针一样引用一片内存区域,所以中间态的uintptr变量保存的就是一个“数”,当Go runtime 开心时,对垃圾进行回收,根本不会顾及到uintptr的引用,虽然runtime中有些syscall方法中的参数也是uintptr类型,但编译器会对其进行特殊处理,当uintptr指向的内存被回收后,你仍操作该内存,会产生十分诡异的问题,像野指针一样,不容易排查。
再闲扯两句
Go语言出身名门,最初是业内的几位泰斗级的大佬亲自操刀,不可否认的,这是一门优秀的语言。但在逐渐的深入了解后你会发现,其封装程度非常之高,当你遇见性能瓶颈的时候,可优化的余地非常之有限。Go有很多适合自己的领域,但其不是万能的。GC很爽,但也很拖后腿。若想让一个Go函数效率更高一些怎么做?答案是用汇编重写该函数…Go使用的伪汇编是古老的plan 9操作系统的plan 9汇编。

Reference
https://pkg.go.dev/unsafe@go1.19.4#Sizeof

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值