你不知道的Go unsafe.Pointer uintptr原理和玩法
unsafe.Pointer
这个类型比较重要,它是实现定位和读写的内存的基础,Go runtime大量使用它。官方文档对该类型有四个重要描述:
(1)任何类型的指针都可以被转化为Pointer
(2)Pointer可以被转化为任何类型的指针
(3)uintptr可以被转化为Pointer
(4)Pointer可以被转化为uintptr
大多数指针类型会写成T,表示是“一个指向T类型变量的指针”。unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void类型的指针),它可以包含任意类型变量的地址。当然,我们不可以直接通过*p来获取unsafe.Pointer指针指向的真实变量的值,因为我们并不知道变量的具体类型。和普通指针一样,unsafe.Pointer指针也是可以比较的,并且支持和nil常量比较判断是否为空指针。
一个普通的T类型指针可以被转化为unsafe.Pointer类型指针,并且一个unsafe.Pointer类型指针也可以被转回普通的指针,被转回普通的指针类型并不需要和原始的T类型相同。
package main import (
"fmt"
"unsafe"
"reflect"
)
type W struct {
b byte
i int32
j int64
} //通过将float64类型指针转化为uint64类型指针,我们可以查看一个浮点数变量的位模式。
func Float64bits(f float64) uint64 {
fmt.Println(reflect.TypeOf(unsafe.Pointer(&f))) //unsafe.Pointer
fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f)))) //*uint64
return *(*uint64)(unsafe.Pointer(&f))
}
func Uint(i int)uint{
return *(*uint)(unsafe.Pointer(&i))
}
type Uint6 struct {
low []byte
high uint32
}
//func (u *Uint6) SetLow() {
// fmt.Printf("i=%d\n", this.i)
//}
//
//func (u *Uint6) SetHigh() {
// fmt.Printf("j=%d\n", this.j)
//}
func writeByPointer(){
uint6 := &Uint6{}
lowPointer:=(*[]byte)(unsafe.Pointer(uint6))
*lowPointer = []byte{,}
//unsafe.Offsetof会计算padding后的偏移距离
//必须将unsafe.Pointer转化成 uintptr类型才能进行指针的运算,uintptr 与 unsafe.Pointer 之间可以相互转换。
highPointer:=(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(uint6))+unsafe.Offsetof(uint6.high)))
fmt.Printf("addr %x addr %x size %v size %v size %v align %v offset %v \n", uintptr(unsafe.Pointer(uint6)),uintptr(unsafe.Pointer(uint6))+unsafe.Sizeof(uint6.low),unsafe.Sizeof([]byte{,}),unsafe.Sizeof(uint6.low), unsafe.Sizeof(uint6.high), unsafe.Alignof(uint6.low), unsafe.Offsetof(uint6.high))
*highPointer = uint32()
//借助于 unsafe.Pointer,我们实现了像 C 语言中的指针偏移操作。可以看出,这种不安全的操作使得我们可以在任何地方直接访问结构体中未公开的成员,只要能得到这个结构体变量的地址。
fmt.Printf("%+v %v %v %v \n", uint6, &uint6,&uint6.low[], &uint6.high)
}
type T struct {
t1 byte
t2 int32
t3 int64
t4 string
t5 bool
}
func main() {
fmt.Printf("%#x %#b \n", Float64bits(11.3), Float64bits()) // "0x3ff0000000000000"
var intA int =
uintA:=Uint(intA)
fmt.Printf("%#v %v %v \n", intA, reflect.TypeOf(uintA), uintA)
var w W = W{}
//在struct中,它的对齐值是它的成员中的最大对齐值。
fmt.Printf("%v, %v, %v, %v, %v, %v, %v, %v\n", unsafe.Alignof(w), unsafe.Alignof(w.b), unsafe.Alignof(w.i), unsafe.Alignof(w.j), unsafe.Sizeof(w),unsafe.Sizeof(w.b),unsafe.Sizeof(w.i),unsafe.Sizeof(w.j), ) fmt.Println(unsafe.Alignof(byte()))
fmt.Println(unsafe.Alignof(int8()))
fmt.Println(unsafe.Alignof(uint8()))
fmt.Println(unsafe.Alignof(int16()))
fmt.Println(unsafe.Alignof(uint16()))
fmt.Println(unsafe.Alignof(int32()))
fmt.Println(unsafe.Alignof(uint32()))
fmt.Println(unsafe.Alignof(int64()))
fmt.Println(unsafe.Alignof(uint64()))
fmt.Println(unsafe.Alignof(uintptr()))
fmt.Println(unsafe.Alignof(float32()))
fmt.Println(unsafe.Alignof(float64()))
//fmt.Println(unsafe.Alignof(complex(0, 0)))
fmt.Println(unsafe.Alignof(complex64()))
fmt.Println(unsafe.Alignof(complex128()))
fmt.Println(unsafe.Alignof(""))
fmt.Println(unsafe.Alignof(new(int)))
fmt.Println(unsafe.Alignof(struct {
f float32
ff float64
}{}))
fmt.Println(unsafe.Alignof(make(chan bool, )))
fmt.Println(unsafe.Alignof(make([]int, )))
fmt.Println(unsafe.Alignof(make(map[string]string, ))) t := &T{, , , "", true}
fmt.Println("sizeof :")
fmt.Println(unsafe.Sizeof(*t))
fmt.Println(unsafe.Sizeof(t.t1))
fmt.Println(unsafe.Sizeof(t.t2))
fmt.Println(unsafe.Sizeof(t.t3))
fmt.Println(unsafe.Sizeof(t.t4))
fmt.Println(unsafe.Sizeof(t.t5))
//这里以0x0作为基准内存地址。打印出来总共占用40个字节。t.t1 为 char,对齐值为 1,0x0 % 1 == 0,从0x0开始,占用一个字节;t.t2 为 int32,对齐值为 4,0x4 % 4 == 0,从 0x4 开始,占用 4 个字节;t.t3 为 int64,对齐值为 8,0x8 % 8 == 0,从 0x8 开始,占用 8 个字节;t.t4 为 string,对齐值为 8,0x16 % 8 == 0,从 0x16 开始, 占用 16 个字节(string 内部实现是一个结构体,包含一个字节类型指针和一个整型的长度值);t.t5 为 bool,对齐值为 1,0x32 % 8 == 0,从 0x32 开始,占用 1 个字节。从上面分析,可以知道 t 的对齐值为 8,最后 bool 之后会补齐到 8 的倍数,故总共是 40 个字节。 fmt.Println("Offsetof : ")
fmt.Println(unsafe.Offsetof(t.t1))
fmt.Println(unsafe.Offsetof(t.t2))
fmt.Println(unsafe.Offsetof(t.t3))
fmt.Println(unsafe.Offsetof(t.t4))
fmt.Println(unsafe.Offsetof(t.t5)) writeByPointer()
//CPU看待内存是以block为单位的,就像是linux下文件大小的单位IO block为4096一样,
//是一种牺牲空间换取时间的做法, 我们一定要注意不要浪费空间,
//struct类型定义的时候一定要将占用内从空间小的类型放在前面, 充足利用padding, 才能提升内存、cpu效率
}
go run PLAY.go
unsafe.Pointer
*uint64
unsafe.Pointer
*uint64
0x402699999999999a 0b100000000010000000000000000000000000000000000000000000000000000
uint
, , , , , , , sizeof : Offsetof : addr c00008e038 addr c00008e03a size size size align offset
&{low:[ ] high:} 0xc00008a010 0xc00008e038 0xc00008e03c
uintptr
// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr
uintptr是golang的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是,
typedef unsigned long long int uint64;
typedef uint64 uintptr;
一个unsafe.Pointer指针也可以被转化为uintptr类型,然后保存到指针型数值变量中(注:这只是和当前指针相同的一个数字值,并不是一个指针),然后用以做必要的指针数值运算。(uintptr是一个无符号的整型数,足以保存一个地址)这种转换虽然也是可逆的,但是将uintptr转为unsafe.Pointer指针可能会破坏类型系统,因为并不是所有的数字都是有效的内存地址。
许多将unsafe.Pointer指针转为原生数字,然后再转回为unsafe.Pointer类型指针的操作也是不安全的。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为*int16类型指针,然后通过该指针更新x.b:
package main import (
"fmt"
"unsafe"
) func main() { var x struct {
a bool
b int16
c []int
} /**
unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞.
*/ /**
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
指针的运算
*/
// 和 pb := &x.b 等价
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb =
fmt.Println(x.b) // "42"
}
上面的写法尽管很繁琐,但在这里并不是一件坏事,因为这些功能应该很谨慎地使用。不要试图引入一个uintptr类型的临时变量,因为它可能会破坏代码的安全性(注:这是真正可以体会unsafe包为何不安全的例子)。
下面段代码是错误的:
// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
产生错误的原因很微妙。有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时,变量x可能已经被转移,这时候临时变量tmp也就不再是现在的&x.b地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!
总结
第一是 unsafe.Pointer
可以让你的变量在不同的指针类型转来转去,也就是表示为任意可寻址的指针类型。第二是 uintptr
常用于与 unsafe.Pointer
打配合,用于做指针运算,和C (*void)指针一样。
unsafe是不安全的,所以我们应该尽可能少的使用它,比如内存的操纵,这是绕过Go本身设计的安全机制的,不当的操作,可能会破坏一块内存,而且这种问题非常不好定位。
当然必须的时候我们可以使用它,比如底层类型相同的数组之间的转换;比如使用sync/atomic包中的一些函数时;还有访问Struct的私有字段时;该用还是要用,不过一定要慎之又慎。
还有,整个unsafe包都是用于Go编译器的,不用运行时,在我们编译的时候,Go编译器已经把他们都处理了。
你不知道的Go unsafe.Pointer uintptr原理和玩法的更多相关文章
- Go之unsafe.Pointer && uintptr 类型
Go语言是个强类型语言.Go语言要求所有统一表达式的不同的类型之间必须做显示的类型转换.而作为Go语言鼻祖的C语言是可以直接做隐式的类型转换的. 也就是说Go对类型要求严格,不同类型不能进行赋值操作. ...
- golang unsafe.Pointer与uintptr
原文地址:https://blog.fanscore.cn/p/33/ 先说结论 uintptr 是一个地址数值,它不是指针,与地址上的对象没有引用关系,垃圾回收器不会因为有一个uintptr类型的值 ...
- C#语法糖系列 —— 第三篇:聊聊闭包的底层玩法
有朋友好奇为什么将 闭包 归于语法糖,这里简单声明下,C# 中的所有闭包最终都会归结于 类 和 方法,为什么这么说,因为 C# 的基因就已经决定了,如果大家了解 CLR 的话应该知道, C#中的类最终 ...
- Go语言阅读小笔记,来自知呼达达关于unsafe.Pointer的分享.
第一式 - 获得Slice和String的内存数据 func stringPointer(s string) unsafe.Pointer { p := (*reflect.StringHeader) ...
- 使用unsafe.Pointer将结构体转为[]byte
package main import ( "fmt" "unsafe" ) type TestStructTobytes struct { data int6 ...
- 全面解密QQ红包技术方案:架构、技术实现、移动端优化、创新玩法等
本文来自腾讯QQ技术团队工程师许灵锋.周海发的技术分享. 一.引言 自 2015 年春节以来,QQ 春节红包经历了企业红包(2015 年).刷一刷红包(2016 年)和 AR 红包(2017 年)几个 ...
- [深入学习Web安全](11)之XSS玩法
[深入学习Web安全](11)之XSS玩法 本文转自:i春秋社区 前言这篇paper,我们将学习如何优雅的调戏XSS.我们会教大家一些不常用的,但很实用的XSS姿势.我们在正式进入主题之前,先来说一下 ...
- 170315、spring:@Autowired等注解的别样玩法
适用场景: 1.IOC容器完成启动就想加载进来的数据 2.多个已经定义好的组件,想在使用的时候通过一行代码就全部拿到 3.等等.... 联想:@Autowired.@Resources等也可以类似使用 ...
- AB实验的高端玩法系列3 - AB组不随机?观测试验?Propensity Score
背景 都说随机是AB实验的核心,为什么随机这么重要呢?有人说因为随机所以AB组整体不存在差异,这样才能准确估计实验效果(ATE) \[ ATE = E(Y_t(1) - Y_c(0)) \] 那究竟随 ...
随机推荐
- Chrome保存的HAR文件怎么打开?
- Chrome保存HAR 在Chrome中,在需要抓包的任意一个浏览器窗口,按F12,点Network页面,即可进入抓包界面,之后的所有网页交互操作产生的报文,都会在此列出. 在抓包的报文界面上右键 ...
- (一)创建新的react native 应用程序
最近开始学习ReactNative了,首先了解下ReactNative http://wiki.jikexueyuan.com/project/react-native/GettingStarted. ...
- linux权限管理-特殊权限
目录 linux权限管理-特殊权限 一,特殊权限 Linux权限属性chattr概述 linux进程掩码umask linux权限管理-特殊权限 一,特殊权限 1.suid(4000) SetUID( ...
- 用Toad for Oracle创建数据库表空间和用户
打开Toad, 1,菜单栏Session—>new Connection….打开如下窗口: 2,进入之后,菜单DatebaseàSechema Brower...找到Table Space(表 ...
- MVC 、MTV 模式
著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层:他们之间以一种插件似的,松耦合的方式连接在一起. 模型负责业务对象与数据库的对象(ORM),视图负责与用户的交 ...
- 4-8 pie与布局
In [1]: %matplotlib inline import matplotlib.pyplot as plt 1.pie简单参数:plt.pie(x, explode=None, labe ...
- JDOJ1100: Fix
题目大意 给你n个点,其中一些点是固定的,然后还有一些没有固定的,然后问你固定所有点所用的线段的最小长度是多少. 所谓固定,就是形如三角形的情况,就是两个固定的点向一个未固定的点连两条边,就能把未固定 ...
- Centos 7 LAMP+wordpress
一.简介 LAMP--->Linux(OS).Apache(http服务器),MySQL(有时也指MariaDB,数据库) 和PHP的第一个字母,一般用来建立web应用平台. 它是 ...
- c# 第39节 抽象类、抽象方法
本节内容: 1:抽象类的说明 2:抽象类的实例 1:抽象类的说明 抽象类定义:方法前有abstract就称为抽象类.抽象方法,抽象方法不提供任何实际实现. 注意点1: 抽象方法必须在抽象类中声明: 不 ...
- CME Futures & Options Order Book
http://algo-logic.com/futures-options-orderbook Algo-Logic Systems’ Futures & Options (F&O) ...