写点什么

标准库 unsafe:带你突破 golang 中的类型限制

  • 2024-03-29
    广东
  • 本文字数:4965 字

    阅读完需:约 16 分钟

标准库unsafe:带你突破golang中的类型限制

本文分享自华为云社区《突破语言golang中的类型限制》,作者:码乐。

1 简介


在使用 c 语言编程时,常常因为类型的问题大伤脑筋,而其他语言比如 java,python 默认类型又是难以改变的,golang 提供了一些方式用于喜欢 hack 的用户。


2 标准库 unsafe 的简单介绍


官方说明标准库 unsafe 包含绕过 Go 程序的类型安全的操作。


导入 unsafe 包可能是不可移植的,并且不受 Go 1 兼容性指南的保护。


在 1.20 中,标准库的 unsafe 包很小, 二个结构体类型,八个函数,在一个文件中。


	package unsage
type ArbitraryType int type IntegerType int type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr
func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte
复制代码


unsafe 包定义了 二个类型和 八个函数,二个类型 ArbitraryType 和 IntegerType 不真正属于 unsafe 包,我们在 Go 代码中并不能使用它们定义变量。


它表示一个任意表达式的类型,仅用于文档目的,Go 编译器会对其做特殊处理。


虽然位于 unsafe,但是 Alignof,Offsetof,Sizeof,这三个函数的使用是绝对安全的。 以至于 Go 设计者 Rob pike 提议移走它们。


这三个函数的共同点是 都返回 uintptr 类型。


之所以使用 uintptr 类型而不是 uint64 整型,因为这三个函数更多应用于 有 unsafe.Pointer 和 uintptr 类型参数的指针运算。


采用 uintptr 做为返回值类型可以减少指针运算表达式的显式类型转换。

2.1 获取大小 Sizeof


Sizeof 用于获取一个表达式的大小。 该函数获取一个任意类型的表达式 x,并返回 按 bytes 计算 的大小,假设变量 v,并且 v 通过 v =x 声明。


Sizeof 接收任何类型的表达式 x,并返回以 bytes 字节为单位的大小, 并且假设变量 v 是通过 var v = x 声明的。该大小不包括任何可能被 x 引用的内存。


例如,如果 x 是一个切片,Sizeof 返回切片描述符的大小,而不是该片所引用的内存的大小。对于一个结构体,其大小包括由字段对齐引入的任何填充。


如果参数 x 的类型没有变化,不具有可变的大小,Sizeof 的返回值是一个 Go 常数不可变值 。(如果一个类型是一个类型参数,或者是一个数组,则该类型具有可变的大小或结构类型中的元素大小可变)。


示例:


	var (		i  int = 5		a      = [10]int{}		ss     = a[:]		f  FuncFoo
preValue = map[string]uintptr{ "i": 8, "a": 80, "ss": 24, "f": 48, "f.c": 10, "int_nil": 8, } )
type FuncFoo struct { a int b string c [10]byte d float64 } func TestFuncSizeof(t *testing.T) { defer setUp(t.Name())() fmt.Printf("\tExecute test:%v\n", t.Name())
if unsafe.Sizeof(i) != preValue["i"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t) }
if unsafe.Sizeof(a) != preValue["a"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["a"]), t)
}
if unsafe.Sizeof(ss) != preValue["ss"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["ss"]), t)
} if unsafe.Sizeof(f) != preValue["f"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f"]), t)
} if unsafe.Sizeof(f.c) != preValue["f.c"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f.c"]), t)
} if unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) != preValue["int_nil"] { ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
} }
复制代码


Sizeof 函数不支持之间传入无类型信息的 nil 值,如下错误


unsafe.Sizeof(nil)  
复制代码


我们必须显式告知 Sizeof 传入的 nil 究竟是那个类型,


unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) 
复制代码


必须显式告知 nil 是哪个类型的 nil,这就是传入一个值 nil 但是类型明确的变量。


对齐系数 Alignof 用于获取一个表达式的内地地址对齐系数,对齐系数 alignment factor 是一个计算机体系架构 computer architecture 层面的术语。


在不同计算机体系中,处理器对变量地址都有对齐要求,即变量的地址必须可被该变量的对齐系数整除。


它接收一个任何类型的表达式 x,并返回所需的排列方式 假设变量 v 是通过 var v = x 声明的。它是 m 一个最大的值。


例 1,


		a      = [10]int{}
reflect.TypeOf(x).Align() //8 unsafe.Alignof(a) //8
复制代码


它与 reflect.TypeOf(x).Align()返回的值相同。


作为一个特例,如果一个变量 s 是结构类型,f 是一个字段,那么 Alignof(s.f)将返回所需的对齐方式。


该类型的字段在结构中的位置。这种情况与 reeflect.TypeOf(s.f).FieldAlign()返回的值。


Alignof 的返回值是一个 Go 常数,如果参数的类型不具有可变大小。(关于可变大小类型的定义,请参见[Sizeof]的描述)。


继上 例 2:


 	 var (		i  int = 5		a      = [10]int{}		ss     = a[:]		f  FuncFoo		zhs = "文"
preValue = map[string]uintptr{ "i": 8, "a": 80, "ss": 24, "f": 48, "f.c": 10, "int_nil": 8, } )
func TestAlignof(t *testing.T) {
defer setUp(t.Name())() fmt.Printf("\tExecute test:%v\n", t.Name())
var x int
b := uintptr(unsafe.Pointer(&x))%unsafe.Alignof(x) == 0 t.Log("alignof:", b)
if unsafe.Alignof(i) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
}
if unsafe.Alignof(a) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
}
if unsafe.Alignof(ss) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
}
if unsafe.Alignof(f.a) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
}
if unsafe.Alignof(f) != preValue["i"] { ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)
}
复制代码


中文对齐系数 为 8


  if unsafe.Alignof(zhs) != preValue["i"] {    ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t)  }
复制代码


空结构体对齐系数 1


  if unsafe.Alignof(struct{}{}) != 1 {    ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)  }
复制代码


byte 数组对齐系数为 1


  if unsafe.Alignof(sbyte) != 1 {    ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)  }
复制代码


长度为 0 的数组,与其元素的对齐系数相同


  if unsafe.Alignof([0]int{}) != 8 {    ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 8), t)  }
复制代码


长度为 0 的数组,与其元素的对齐系数相同


  if unsafe.Alignof([0]struct{}{}) != 1 {    ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)  }
}
复制代码


执行它:


go test -timeout 30s -run ^TestAlignof$ ./unsafe_case.go
复制代码


对齐系数 alignment factor,变量的地址必须可被该变量的对齐系数整除。

2.2 使用对齐的例子


我们使用相同字段,分别创建两个结构体属性分别为对齐或不对齐,帮助 go 更好地分配内存和 使用 cpu 读取,查看效果


	type RandomResource struct {		Cloud               string // 16 bytes		Name                string // 16 bytes		HaveDSL             bool   //  1 byte		PluginVersion       string // 16 bytes		IsVersionControlled bool   //  1 byte		TerraformVersion    string // 16 bytes		ModuleVersionMajor  int32  //  4 bytes	}
type OrderResource struct { ModuleVersionMajor int32 // 4 bytes HaveDSL bool // 1 byte IsVersionControlled bool // 1 byte Cloud string // 16 bytes Name string // 16 bytes PluginVersion string // 16 bytes TerraformVersion string // 16 bytes
}
复制代码


字段 存储使用的空间与 字段值没有关系


		 var d RandomResource		 d.Cloud = "aws-singapore"		 ...
InfoHandler(fmt.Sprintf("随机顺序属性的结构体内存 总共占用 StructType: %T => [%d]\n", d, unsafe.Sizeof(d)), m) var te = OrderResource{} te.Cloud = "aws-singapore" ... m.Logf("属性对齐的结构体内存 总共占用 StructType:d %T => [%d]\n", te, unsafe.Sizeof(te))
复制代码


然后复制结构体,并改变其属性值,查看存储空间和值的长度变化


		te2 := te		te2.Cloud = "ali2"		m.Logf("结构体2 te2:%#v\n", &te2)		m.Logf("结构体1 te:%#v\n", &te)
m.Log("改变 te3 将同时改变 te,te3 指向了 te的地址") m.Log("复制了对齐结构体,并重新赋值,用于查看字段长度。") m.Log("(*te).Cloud:", (te).Cloud, "*te.Cloud", te.Cloud, "te size:", unsafe.Sizeof(te.Cloud), "te value len:", len(te.Cloud))
te3 := &te te3.Cloud = "HWCloud2"
m.Log("(*te3).Cloud:", (*te3).Cloud, "*te3.Cloud", te3.Cloud, "te3 size:", unsafe.Sizeof(te3.Cloud), "te3 value len:", len(te3.Cloud)) m.Logf("字段 Cloud:%v te3:%p\n", (*te3).Cloud, te3) m.Logf("字段 Cloud:%v order:%v te:%v, addr:%p\n", te.Cloud, (te).Cloud, te, &te)
复制代码


执行它,


go test -v .\case_test.go
复制代码


得到以下输出:


随机顺序属性的结构体内存 总共占用 StructType: main.Raesource => [88]


...
复制代码


属性对齐的结构体内存 总共占用 StructType:d main.OrderResource => [72]


改变 te3 将同时改变 te,te3 指向了 te 的地址


    case_test.go:186: 复制了对齐结构体,并重新赋值,用于查看字段长度。
case_test.go:188: (*te).Cloud: aws-singapore *te.Cloud aws-singapore te size: 16 te Alignof: 8 te value len: 13 reflect Align len and field Align len: 8 8 case_test.go:190: (*te2).Cloud: ali2 *te2.Cloud aws-singapore te2 size: 16 te2 Alignof: 8 te2 value len: 4 reflect Align len and field Align len: 8 8 case_test.go:196: (*te3).Cloud: HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company *te3.Cloud HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3 size: 16 te3 Alignof: 8 te3 value len: 105 reflect Align len and field Align len: 8 8
case_test.go: 结构体1字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te2:0xc0000621e0 case_test.go:198: 结构体2字段 Cloud:ali2 te2:0xc000062280 case_test.go:199: 结构体3字段 Cloud:HWCloud2-asia-southeast-from-big-plant-place-air-local-video-service-picture-merge-from-other-all-company te3:0xc0000621e0
复制代码

小结


我们介绍了 unsafe 包的检查功能,在初始化时,go 结构体已经分配了对于的内存空间,一个结构体而言,结构体属性为随机顺序的,go 将分配更多内存空间。 即使是复制后。


比如 结构体的 Cloud 字段。


Sizeof 表达式大小总是 16,而对齐系数 Alignof 大小总是 8,而在不同的结构体实例中值长度可以为 4,13, 105.


本节源码地址:


https://github.com/hahamx/examples/tree/main/alg_practice/2_sys_io
复制代码


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 5
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
标准库unsafe:带你突破golang中的类型限制_Go_华为云开发者联盟_InfoQ写作社区