ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

golang低级编程:一.unsafe包

2022-03-02 01:04:19  阅读:184  来源: 互联网

标签:fmt 编程 unsafe golang Println 内存 类型 Pointer


go语言在设计上确保了一些安全的属性,限制了程序可能出错的途径。例如严格的类型转换规则。但也使得很多实现的细节无法通过go程序来访问,例如对于聚合类型(如结构体)的内存布局,或者一个函数对应的机器码。

这里我们将讨论unsafe包,它是由编译器实现的,实现了对语言内置特性的访问功能,这些特性一般是不可见的,因为它们暴露了go详细的内存布局。虽然包的名字叫unsafe,但是这些函数本身是安全的,并且在做内存优化的时候,它们对理解函数底层内存布局很有帮助。

unsafe.Sizeof

unsafe.Sizeof 报告传递给它的参数在内存中所占的字节长度,这个参数可以是任意类型的表达式。Sizeof仅会报告每个数据结构固定部分的内存所占字节长度,例如指针或者字符串所占的长度,但不会报告例如字符串内容的间接长度。为了可移植性,以字来表示引用类型的长度或者包含引用类型的长度,在32位系统上字的长度是4个字节,而在64位系统上字的长度是8个字节。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var x struct{
		a bool
		b int16
		c []int
	}

	fmt.Println(unsafe.Sizeof(x.a))
	fmt.Println(unsafe.Sizeof(x.b))
	fmt.Println(unsafe.Sizeof(x.c))
	fmt.Println(unsafe.Sizeof(x))
}
// 64位机器结果:
// 1  : 1个字节,bool
// 2  : 两个字节,16/8 = 2
// 24 : 切片24个字节,3个字,因为切片包含一个指针,一个长度,一个容量。
// 32 :前面两个加上内存空位后就是一个字,8个字节。所以 8 + 24 = 32

如果b和c交换位置,那么内存空位将会更大,8 + 24 + 8 = 40

func main() {
	var x struct{
		a bool
		c []int
		b int16
	}

	fmt.Println(unsafe.Sizeof(x.a))
	fmt.Println(unsafe.Sizeof(x.c))
	fmt.Println(unsafe.Sizeof(x.b))
	fmt.Println(unsafe.Sizeof(x))
}
// 64位机器结果:
// 1
// 24
// 2
// 40   这里8 + 24 = 32和我们的猜想一样。

unsafe.Alignof

unsafe.Alignof报告参数类型所要求的对齐方式。这个参数可以是任意类型的表达式,并返回一个常量。布尔类型和数值类型对齐到它们的长度(最大8个字节),其他类型按字对齐。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var x struct{
		a bool
		b int16
		c []int
	}

	fmt.Println(unsafe.Alignof(x.a))
	fmt.Println(unsafe.Alignof(x.b))
	fmt.Println(unsafe.Alignof(x.c))
	fmt.Println(unsafe.Alignof(x))
}

// 64位机器结果:
// 1  :这是布尔类型,布尔类型和数值类型对齐到它们长度
// 2  :这里数值类型,同上
// 8  :其他类型按字对齐,在64位机器上,一个字是8个字节
// 8

unsafe.Offsetof(f)

计算成员f相对于结构体的起始地址的偏移量。如果有内存空位也计算在内,该函数的操作数,必须是一个成员选择器:x.a。

func main() {
	var x struct{
		a bool
		c []int
		b int16
	}

	fmt.Println(unsafe.Offsetof(x.a))
	fmt.Println(unsafe.Offsetof(x.c))
	fmt.Println(unsafe.Offsetof(x.b))
}
// 64位机器结果:
// 0 : 结构体的第一个成员
// 8 :这里有7个字节的内存空位
// 32

unsafe.Pointer

unsafe.Pointer是一种特殊类型的指针,它可以存储任何变量的地址。对于一个unsafe.Pointer类型的指针,由于我们不知道它的具体类型,导致我们不能间接的通过*p来获取它的实际值。普通类型的指针也可以转换为unsafe.Pointer类型的指针,unsafe.Pointer类型的指针可以转换为普通类型的指针,而且不必和原来的类型相同。使用unsafe.Pointer进行类型转换可以将任意的值写入内存,并因此破坏类型系统。

uintper类型

uintper类型保存了指针所指向地址的数值,这样就可以进行数值运算。(uintpter类型是一个足够大的无符号整型,可以用来表示任何地址。)unsafe.Pointer也可以转换为uintptr,当然uintptr也可以转换为unsafe.Pointer(这里也会破坏类型系统)。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var x struct{
		a bool
		b int16
		c []int
	}

	//pb := &x.b
	pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))

	*pb = 42
	fmt.Println(x.b)
}

这里首先将x的地址转换为unsafe.Pointer类型从而转换为uintptr类型,uintptr类型就可以用于计算。计算后再转换为原来的类型,对内存地址指向的区域赋值。但是这里不能引入uintptr类型的临时变量,例如下面这样。因为垃圾回收器会移动内存中的变量,为了减少内存碎片。但是在垃圾回收器uintptr类型仅仅是一个数值,所以移动过后,不会改变uintptr存的指针对应的内存里的数据。

    ptr := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
    pb := (*int16)(unsafe.Pointer(ptr))

总结

unsafe包可以用来操作内存,从而增加go语言的灵活性,但是unsafe包无法保证在未来go语言升级中能够兼容。uintptr类型不能作为临时变量。并且在uintptr类型转换到unsafe.Pointer的过程中要尽量减少uintptr的操作次数。

标签:fmt,编程,unsafe,golang,Println,内存,类型,Pointer
来源: https://www.cnblogs.com/ourongxin/p/15953260.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有