遇到一段无法理解的代码,求解惑!
go版本 1.22.0
package main
import (
"fmt"
"unsafe"
)
func main() {
var a int64 = 1
var b int64 = 1
fmt.Println(&a, &b)
NilIsNil(a)
var d = make([]int, 10)
var c int64 = 1
NilIsNil2(b)
fmt.Println(a, b, c, d)
var e int64 = 1
// 打开行代码fmt.Println(&a)输出 10 10 10 [0 0 0 0 0 0 0 0 0 0] 10
// 注释fmt.Println(&a)输出 1 1 1 [0 0 0 0 0 0 0 0 0 0] 1
fmt.Println(a, b, c, d, e)
}
func NilIsNil(a any) bool {
// 打开行代码输出 10 10 10 [0 0 0 0 0 0 0 0 0 0] 10
// 注释输出 1 1 1 [0 0 0 0 0 0 0 0 0 0] 1
fmt.Println(&a)
type rtype struct {
t unsafe.Pointer
data unsafe.Pointer
}
t := (*rtype)(unsafe.Pointer(&a))
*(*int)(t.data) = 10
return a == nil
}
func NilIsNil2(a any) bool {
type rtype struct {
t unsafe.Pointer
data unsafe.Pointer
}
t := (*rtype)(unsafe.Pointer(&a))
fmt.Println("val = ", *(*int)(t.data))
return a == nil
}
在一个群里看见的一段代码,发代码的人说是因为golang缓存了128以下的数字,然后这段代码修改的缓存。但是还是有一些地方不理解。
- 为什么fmt.Println(&a)这个行代码会影响输出结果,这行代码不就是一个打印地址的吗?
- golang真的会缓存128以下的数字吗,内部是如何实现的?
这个结构体有什么含义吗type rtype struct { t unsafe.Pointer data unsafe.Pointer }
本作品采用《CC 协议》,转载必须注明作者和本文链接
同不理解,求教
GPT的回答不知道对不对:
不是a的问题,不知道为啥只要这里面有输出语句就会输出10
猜测使用了NilIsNil 方法修改值后可能需要调用一下a或者&a才能生效
在只输出普通的字符串时不输出a,a的结果是1,在输出了a之后结果变成了10
这种也会修改a的值
造成这种现象有2个原因:
1.fmt.Println的问题,打印变量时,变量会转为接口变量
2.接口类型的元数据类型有两个指针,其中data的取值调用了runtime.convT64(),convT64()中所有小于256数值有同一个staticuint64s的数组,可以认为是一种缓存,主要是为了避免非空接口数值类型频繁的堆分配,staticuint64s数组下标等于值,而NilIsNil()通过unsafe把staticuint64s的数组下标为1的空间指向的值改成了10,会导致其他变量转接口变量赋值时下标为1的指向空间的值都是10。
接口类型有个规定是存储在接口的值类型是不可寻址的,而用了unsafe.Pointer强制修改就会导致这种现象
comate.baidu.com/?inviteCode=1k1z4... 装一个 comate 插件,然后直接问插件,帮你问是这样的结果
<= 255
的值都是同样的结果,>=256
的值内部修改的值都不会反应到外部。(感兴趣的可以自己试试)通过上图可以得到结论:
打开打印,a 被修改为 10 逃逸到
heap
,外部打印时,由于所有的变量都逃逸到heap
,由于缓存(这个原理我是不清楚的哈,可以肯定的是, abcd 的指针指向的都是同一个值),abcd 都是 10。关闭打印,a 的值被修改为 10, 未逃逸。 外部打印时,所有的变量都逃逸到
heap
,abcd 都是 1(未被修改)。逃逸分析:
go run -gcflags='-m' main.go
咳咳,该点赞了哈,骚年~
应该说是,当值类型的变量发生逃逸时,会在堆上分配内存,数字类型的逃逸到堆上时,会调用convT64函数,在堆上分配一块内存,而go语言对于<=255数值的做了缓存,所以两个变量的值一样时,是在分配的同一块内存,然后你修改一个,另一个也会跟着变化
千言万语,最后归结为下面一段代码:
现在的问题是:
eval
中,p.arg
会导致 变量m
逃逸。为什么?l, n
,使用fmt.Println
打印时,会受到逃逸变量m
影响,变成 10,而使用println
打印时,m,l,n
三个变量的值仍然为 1。为什么?foo
函数中,接口类型变量a
通过结构体类型S
强制转换时,接口变量a
中,存储的两个指针(第一个指针表示接口中存储变量m
类型信息的内存地址,第二个指针表示接口中存储变量m
值的内存地址), 第一个指针赋给字段t
,第二个指针赋给字段data
。为什么?m,l,n
三个变量的值相同,且均小于255的情况下,使用eval
函数时,代码如下:那么在
eval
函数中,println(a),打印的值(都是指针)均相同。为什么?我引用了你的博客