Go的内存对齐和指针运算详解和实践 - Go语言中文网 - Golang中文社区

主题 文章 项目 资源 图书 Go周刊 实战课程 下载 官方文档 英文文档 中文文档 标准库中文版 Go指南 注册 登录
分享
首页 文章
Go的内存对齐和指针运算详解和实践 SunPengWei · · 133 次点击 · · 开始浏览    
#### uintptr 和 unsafe普及 ##### uintptr 在Go的源码中uintptr的定义如下: ```go /* uintptr is an integer type that is large enough to hold the bit pattern of any pointer. 从英文注释可以看出 uintptr是一个整形,它的大小能够容纳任何指针的位模式,它是无符号的,最大值为:18446744073709551615,怎么来的,int64最大值 * 2 +1 */ type uintptr uintptr ``` **位模式**:内存由字节组成.每个字节由8位bit组成,每个bit状态只能是0或1.所谓位模式,就是变量所占用内存的所有bit的状态的序列 **指针大小**:一个指针的大小是多少呢?在32位操作系统上,指针大小是4个字节,在64位操作系统上,指针的大小是8字节, **所以uintptr能够容纳任何指针的位模式,总的说uintptr表示的指针地址的值,可以用来进行数值计算** GC不会把uintptr当作指针,uintptr不会持有一个对象,uintptr类型的目标会被GC回收 ##### unasfe 在Go中,unsafe是一个包,内容也比较简短,但注释非常多,这个包主要是用来在一些底层编程中,让你能够操作内存地址计算,也就是说Go本身是不支持指针运算,但还是留了一个后门,而且Go也不建议研发人员直接使用unsafe包的方法,因为它绕过了Go的内存安全原则,是不安全的,容易使你的程序出现莫名其妙的问题,不利于程序的扩展与维护但为什么说它呢,因为很多框架包括SDK中的源代码都用到了这个包的知识,在看源代码时这块不懂,容易懵。下面看看这个包定义了什么? ```go //ArbitraryType的类型也是int,但它被赋予特殊的含义,代表一个Go的任意表达式类型 type ArbitraryType int //Pointer是一个int指针类型,在Go种,它是所有指针类型的父类型,也就是说所有的指针类型都可以转化为Pointer, uintptr和Pointer可以相互转化 type Pointer *ArbitraryType //返回指针变量在内存中占用的字节数(记住,不是变量对应的值占用的字节数) func Sizeof(x ArbitraryType) uintptr /*Offsetof返回变量指定属性的偏移量,这个函数虽然接收的是任何类型的变量,但是有一个前提,就是变量要是一个struct类型,且还不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数*/ func Offsetof(x ArbitraryType) uintptr //返回变量对齐字节数量 func Alignof(x ArbitraryType) uintptr ``` #### 什么是内存对齐?为什么要内存对齐? 在我了解比较深入的语言中(Java Go)都有内存对齐的概念,百度百科对内存对齐的概念是这样定义的:**“内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上**,所谓的数据单元其实就是变量的值。 **为什么要内存对齐呢?** 1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常(32位平台上运行64位平台上编译的程序要求必须8字节对齐,否则发生panic) 2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问 **对齐规则**:也就是对齐的边界,多少个字节内存对齐,在32位操作系统上,是4个自己,在64位操作系统上是8个字节 **通过一幅图来理解上面的内容,下图只是举个例子,位数并没有画全** ![Image.png](https://static.studygolang.com/200106/ed198efd58b2ee67dec7ff679d8b9f97.png) #### 指针运算和内存对齐实践 ##### 内存对齐实践 理论总是枯燥的,但必须了解,也许看了理论还是不懂,接下来通过实践让你明白 ```go //创建一个变量 var i int8 = 10 //建一个变量转化成Pointer 和 uintptr p := unsafe.Pointer(&i) //入参必须是指针类型的 fmt.Println(p) //是内存地址0xc0000182da u := uintptr(i) fmt.Println(u) //结果就是10 //Pointer转换成uintptr temp := uintptr(p) //uintptr转Pointer p= unsafe.Pointer(u) //获取指针大小 u = unsafe.Sizeof(p) //传入指针,获取的是指针的大小 fmt.Println(u) // 打印u是:8 //获取的是变量的大小 u = unsafe.Sizeof(i) fmt.Println(u) //打印u是:1 //创建两个个结构体 type Person1 struct{ a bool b int64 c int8 d string } type Person2 struct{ b int64 c int8 a bool d string } //接下来演示一下内存对齐,猜一猜下面l两个打印值是多少呢? person1 := Person1{a:true,b:1,c:1,d:"spw"} fmt.Println(unsafe.Sizeof(person1)) person2 := Person2{b:1,c:1,a:true,d:"spw"} fmt.Println(unsafe.Sizeof(person2)) //第一个结果是40,第二个结果是32,为什么会有这些差距呢?其实就是内存对齐做的鬼,我来详细解释一下 ``` 我们知道在Person1和Person2种变量类型都一样,只是顺序不太一样, bool占1个字节, int64占8个字节, int8占一个字节, string占用16个字节, 总的结果应该是 1+8+1+16= 26,为啥Person1是40呢,Person2是32,看下图 ![Image [2].png](https://static.studygolang.com/200106/a076bd08fe104ba866a84efb58fbf2ff.png) 根据上图,我们就明白了,在结构体编写中存在内存对齐的概念,而且我们应该小心,尽可能的避免因内存对齐导致结构体大小增大,在书写过程中应该让小字节的变量挨着。我们可以工具进行检测(golangci-lint)。 我们可以通过`func Alignof(x ArbitraryType) uintptr`这个方法返回内存对齐的字节数量,如下代码 ```go type Person1 struct{ a bool b int64 c int8 d string } p := Person{a:true,b:1,c:1,d:"spw"} fmt.Println(unsafe.Alignof(person)) type Person2 struct{ a bool c int8 } p1 := Person1{a:true,b:1,c:1,d:"spw"} fmt.Println(unsafe.Alignof(p1)) p2 := Person2{a:true,c:1} fmt.Println(unsafe.Alignof(p2)) //你任务上面两个println打印多少呢?结果是8,1,在结构体中,内存对齐是按照结构体中最大字节数对齐的(但不会超过8) ``` ##### 指针运算实践 我们还是用代码来举例说明 ```go type W struct { b int32 c int64 } var w *W = new(W) //这时w的变量打印出来都是默认值0,0 fmt.Println(w.b,w.c) //现在我们通过指针运算给b变量赋值为10 b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w.b)) *((*int)(b)) = 10 //此时结果就变成了10,0 fmt.Println(w.b,w.c) ``` 解释一下上面的代码 `uintptr(unsafe.Pointer(w))`获取了w的指针起始值, `unsafe.Offsetof(w.b)` 获取b变量的偏移量 两个相加就得到了b的地址值,将通用指针Pointer转换成具体指针`((*int)(b))`,通过 * 符号取值,然后赋值,*((*int)(b)) 相当于把(*int) 转换成 int了,最后对变量重新赋值成10,这样指针运算就完成了。 **欢迎关注公众号,阅读更多精彩文章** ![技术人技术事.jpg](https://static.studygolang.com/190721/c55fa00b6c19806beda719ee62847c9f.jpg)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:muxilin131420 备注:入群;或加QQ群:729884609

133 次点击  
加入收藏 微博
收入我的专栏
上一篇: 再学JavaScript ES(6-10)全版本语法大全
下一篇: Golang cron 定时任务使用
变量 代码 字节数 偏移量
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
编辑 预览
请尽量让自己的回复能够对别人有帮助 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码` 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet 图片支持拖拽、截图粘贴等方式上传
提交
用户登录
记住登录状态
没有账号? 注册
忘记密码?
GitHub 登录
Gitea 登录
 今日阅读排行
 一周阅读排行
  关注我
扫码关注领全套学习资料 加入 QQ 群: 192706294(已满) 731990104(已满) 798786647(已满) 729884609 加入微信群:mylovezilv,备注入群 也欢迎加入知识星球 Go粉丝们(免费)
× 给该专栏投稿 写篇新文章 每篇文章有总共有 5 次投稿机会
× 收入到我管理的专栏 新建专栏
关于   •   FAQ   •   贡献者   •   晨读   •   Github   •   新浪微博   •   Play   •   免责声明   •   联系我们   •   捐赠   •   酷站   •   Feed订阅   •   2430 人在线   最高记录 4314
©2013-2020 studygolang.com Go语言中文网,中国 Golang 社区,致力于构建完善的 Golang 中文社区,Go语言爱好者的学习家园。
Powered by StudyGolang(Golang + MySQL)   •  · CDN 采用 七牛云
VERSION: V4.0.0 · 14.767619ms · 为了更好的体验,本站推荐使用 Chrome 或 Firefox 浏览器
京ICP备14030343号-1
X
登录和大家一起探讨吧
用户名
密码
记住登录状态 登录
GitHub 登录
忘记密码?
还不是会员 现在注册

友情链接:
三维推
  
牛x网网站地图

扫码查看移动端

返回
首页