Go 是如何确保内存安全的? go操作内存
yuyutoo 2024-12-15 17:41 4 浏览 0 评论
Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.
??这篇文章基于 Go 1.13 编写。
Go 的一系列内存管理手段(内存分配,垃圾回收,内存访问检查)使许多开发者的开发工作变得很轻松。编译器通过在代码中引入“边界检查” 来确保安全地访问内存。
生成的指令
Go 引入了一些控制点位,来确保我们的程序访问的内存片段安全且有效的。让我们从一个简单的例子开始:
package main
func main() {
list := []int{1, 2, 3}
printList(list)
}
func printList(list []int) {
println(list[2])
println(list[3])
}
这段代码跑起来之后会 panic:
3
panic: runtime error: index out of range [3] with length 3
Go 通过添加边界检查来防止不正确的内存访问
如果你想知道没有这些检查会怎么样,你可以使用 -gcflags="-B" 的选项,输出如下
3
824633993168
因为这块内存是无效的,它会读取不属于这个 slice 的下一个 bytes。
利用命令 go tool compile -S main.go 来生成对应的汇编[1]代码,就可以看到这些检查点:
0x0021 00033 (main.go:10) MOVQ "".list+48(SP), CX
0x0026 00038 (main.go:10) CMPQ CX, $2
0x002a 00042 (main.go:10) JLS 161
[...] here Go prints the third element
0x0057 00087 (main.go:11) MOVQ "".list+48(SP), CX
0x005c 00092 (main.go:11) CMPQ CX, $3
0x0060 00096 (main.go:11) JLS 151
[...]
0x0096 00150 (main.go:12) RET
0x0097 00151 (main.go:11) MOVL $3, AX
0x009c 00156 (main.go:11) CALL runtime.panicIndex(SB)
0x00a1 00161 (main.go:10) MOVL $2, AX
0x00a6 00166 (main.go:10) CALL runtime.panicIndex(SB)
Go 先使用 MOVQ 指令将 list 变量的长度放入寄存器 CX 中
0x0021 00033 (main.go:10) MOVQ "".list+48(SP), CX
友情提醒,slice 类型的变量由三部分组成,指向底层数组的指针、长度,容量(capacity)。list 变量在栈中的位置如下图:
通过将栈指针移动 48 个字节就可以访问长度
下一条指令将 slice 的长度与程序即将访问的偏移量进行比较
CMPQ 指令会将两个值相减,并在下一条指令中与 0 进行比较。如果 slice 的长度(寄存器 CX)减去要访问的偏移量(在这个例子当中是 2)小于或等于 0(JLS 是 Jump on lower or the same 的缩写),程序就会跳到 161 处继续执行。
两种边界检查使用的都是相同的指令。除了看生成的汇编代码,Go 提供了一个编译期的通行证去打印出边界检查的点,你可以在 build 和 run 的时候使用标志 -gcflags="-d=ssa/check_bce/debug=1" 去开启。输出如下:
./main.go:10:14: Found IsInBounds
./main.go:11:14: Found IsInBounds
我们可以看到输出里生成了两个检查点。不过 Go 编译器足够聪明,在不需要的情况下,它不会生成边界检查的指令。
规则
在每次访问内存的时候都生成检查指令是非常低效的,让我们稍微修改一下前面的例子。
package main
func main() {
list := []int{1, 2, 3}
printList(list)
}
func printList(list []int) {
println(list[3])
println(list[2])
}
两个 println 指令对调了,用 check_bce 标志再去跑一遍程序,这次只有一处边界检查:
./main.go:11:14: Found IsInBounds
程序先检查了偏移量 3 。如果是有效的,那么 2 很明显也是有效的,没必要再去检查了。可以通过命令 GOSSAFUNC=printList Go run main.go 来生成 SSA 代码看编译过程。这张图就是生成的带边界检查的 SSA 代码:
里面的 prove pass 将边界检查标记为移除,这样后面的 pass 将会收集这些 dead code:
用这条命令 GOSSAFUNC=printList Go run -gcflags="-d=ssa/prove/debug=3" main.go 可以把 pass 背后的逻辑打印出来,它也会生成 SSA 文件来帮助你 debug,接下来看命令的输出:
这个 pass 实际上会采取不同的策略,并建立了 fact 表。这些 fact 决定了矛盾点在哪里。在我们这个例子里,我们可以通过 SSA 的 pass 来解读这些规则:
第一个阶段从代表指令 println(list[3]) 的分析块 b1 开始,这个指令有两种可能:
- 偏移量 [3] 在边界中,跳到第二个指令 b2。在这个例子中,Go 指定 v7 的限制(slice 的长度)是 [4, max(int)]。
- 偏移量 [3 不在边界中, 程序跳转到 b3 指令并 panic。
接下来,Go 开始处理 b2 块(第二个指令)。这里也有两种可能
- 偏移量 [2] 在边界中,这意味着 slice 的长度 v7 比 v23(偏移量[2]) 要大。在先前的 b1 块中 Go 已经判断了 v7 > 4, 所以这个已经被确认了。
- 偏移量 [2] 不在边界中,这意味着它比 slice 的长度 v7 更大,但 v7 的限制是 [4, max(int)] ,所以 Go 会将这个分之标记为矛盾,意味着这种情况永远不会发生,这条指令的边界检查可以被移除。
这个 pass 在随着时间不断地改善,现在可以参考更多的 case[2]。消除边界检查可以略微提升 Go 程序的运行速度,但除非你的程序是微妙级敏感的,不然没有必要去优化它。
via: https://medium.com/a-journey-with-go/go-memory-safety-with-bounds-check-1397bef748b5
作者: Vincent Blanchon[3]译者:yxlimo[4]校对:Alex.Jiang[5]本文由GCTT[6] 原创编译,Go 中文网[7] 荣誉推出
参考资料
[1]
汇编: https://golang.org/doc/asm
[2]
更多的 case: https://github.com/golang/go/blob/master/test/prove.go
[3]
Vincent Blanchon: https://medium.com/@blanchon.vincent
[4]
yxlimo: https://github.com/yxlimo
[5]
Alex.Jiang: https://github.com/JYSDeveloper
[6]
GCTT: https://github.com/studygolang/GCTT
[7]
Go 中文网: https://studygolang.com/
相关推荐
- MySQL中的数据类型(mysql数据类型有哪些,并举例)
-
MySQL中的数据类型...
- mysql窗口函数over中rows_MySQL窗口函数
-
下面的讲解将基于这个employee2表:mysql>SELECT*FROMemployee2;+----+-----------+------+---------+---------...
- 别再说你精通数据库,MySQL的设计和列类型选取真的很有讲究
-
总想写一篇MySQL的设计和列类型选取的文章,一直挤不出时间。天天晚上都要加班,正逢5.1放假,抽了几天就有了此文。如果对朋友们能有帮助的话,关注一波不过分吧?求关!选择更优的数据类型尽量选择存储空间...
- MySQL数据库知识(mysql数据库相关知识)
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 数据库:MySQL 高性能优化规范建议
-
数据库命令规范所有数据库对象名称必须使用小写字母并用下划线分割所有数据库对象名称禁止使用MySQL保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)数据库对象的命名要能做到见名识意,...
- MySQL实战——表结构设计之数字类型
-
整型不建议刻意去用unsigned属性,因为在做一些数据分析时,SQL可能返回的结果并不是想要得到的结果。比如在财务的场景下,经常会做一些加减操作。MySQL要求unsigned数值相减之...
- MySQL数据库入门(四)数据类型简介
-
在MySQL中数据类型有以下五种:数字整数:常用的有2种,一是int型,int型最多可以表示10位数字(无符号的4开头,有符号的2开头;二是tinyintunsigned,用来表示年龄(值范围是0-...
- mysql常用语句超级详细汇总(mysql常用语法)
-
1.连接数据库:连接本地数据库:mysql-uroot-p连接远程数据库:mysql-h192.169.22.199-uroot-p退出数据库:exit...
- MYSQL——CAST()函数的用法(mysql中case)
-
语法为:Cast(字段名as转换的类型),其中类型可以为:CHAR[(N)]字符型DATE日期型DATETIME日期和时间型...
- MySQL存储引擎背后的真相:为何InnoDB并非所有场景的最佳选择
-
MySQL存储引擎背后的真相:为何InnoDB并非所有场景的最佳选择引言部分你是否遇到过这样的情况:明明已经按照最佳实践选择了MySQL的InnoDB引擎,却发现某些查询依然缓慢得令人沮丧?或者当你的...
- MySQL 表分区?涨知识了(mysql数据表分区)
-
1.什么是表分区...
- 《MySQL必知必会》_笔记08(mysql必知必会mobi)
-
第19章插入数据一、数据插入概述INSERT语句用于向数据库表中插入(添加)数据,是SQL中常用的数据操作语句之一。它可以用多种方式使用,包括插入完整的行、插入行的一部分、插入多行以及插入某些查询的...
- 当 SQL Server(mssql-jdbc) 遇上 BigDecimal → 精度丢失,真坑!
-
开心一刻 中午和哥们一起喝茶 哥们说道:晚上喝酒去啊...
- MYSQL有哪些数据类型(mysql有哪些数据类型,有哪些运算符)
-
整理下以便查阅,还想吐槽下:这头条怎么就不能给文章分类呢?整数类型...
- 使用MySQL分区的注意事项(使用mysql分区的注意事项有哪些)
-
MySQL分区是将一个表分解成多个区块进行操作和保存,从而降低每次操作的数据量,提高性能。从逻辑上看,只有一个表,但物理上这个表可能由多个物理分区组成,每个分区都是一个独立的对象,可以进行独立处理。...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
推荐7个模板代码和其他游戏源码下载的网址
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)