遇到一段无法理解的代码,求解惑!

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以下的数字,然后这段代码修改的缓存。但是还是有一些地方不理解。

  1. 为什么fmt.Println(&a)这个行代码会影响输出结果,这行代码不就是一个打印地址的吗?
  2. golang真的会缓存128以下的数字吗,内部是如何实现的?
  3. type rtype struct {
         t    unsafe.Pointer
         data unsafe.Pointer
     }
    这个结构体有什么含义吗
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 19

GPT的回答不知道对不对:

file

file

Go

Go

1个月前 评论
don178 (楼主) 1个月前

不是a的问题,不知道为啥只要这里面有输出语句就会输出10

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)
    //fmt.Println(a)
    type rtype struct {
        t    unsafe.Pointer
        data unsafe.Pointer
    }
    out := bufio.NewWriter(os.Stdout)
    defer out.Flush()
    t := (*rtype)(unsafe.Pointer(&a))
    fmt.Fprintln(out, t)
    //fmt.Println(&t)
    //fmt.Println(t)
    *(*int)(t.data) = 10
    //fmt.Println(&t)
    //fmt.Println(t)
    //fmt.Println(a)
    return a == nil
}
1个月前 评论
don178 (楼主) 1个月前

猜测使用了NilIsNil 方法修改值后可能需要调用一下a或者&a才能生效
在只输出普通的字符串时不输出a,a的结果是1,在输出了a之后结果变成了10

file

file

这种也会修改a的值

Go

4周前 评论

file

造成这种现象有2个原因:

1.fmt.Println的问题,打印变量时,变量会转为接口变量

2.接口类型的元数据类型有两个指针,其中data的取值调用了runtime.convT64(),convT64()中所有小于256数值有同一个staticuint64s的数组,可以认为是一种缓存,主要是为了避免非空接口数值类型频繁的堆分配,staticuint64s数组下标等于值,而NilIsNil()通过unsafe把staticuint64s的数组下标为1的空间指向的值改成了10,会导致其他变量转接口变量赋值时下标为1的指向空间的值都是10。

接口类型有个规定是存储在接口的值类型是不可寻址的,而用了unsafe.Pointer强制修改就会导致这种现象

file

4周前 评论
迷途的羔羊 (作者) 4周前
迷途的羔羊 (作者) 4周前
don178 (楼主) 4周前
don178 (楼主) 4周前
迷途的羔羊 (作者) 4周前

comate.baidu.com/?inviteCode=1k1z4... 装一个 comate 插件,然后直接问插件,帮你问是这样的结果

file

4周前 评论
don178 (楼主) 3周前

file

  1. golang 缓存了 128 以下的数字,其实并不是,<= 255 的值都是同样的结果,>=256 的值内部修改的值都不会反应到外部。(感兴趣的可以自己试试)
  2. 借助逃逸分析,你会看到只要调用 fmt.PrintLn 函数都会将变量移动到 heap 上,变量逃逸( xxx escapes to heap)。

通过上图可以得到结论:

  1. 打开打印,a 被修改为 10 逃逸到 heap,外部打印时,由于所有的变量都逃逸到 heap,由于缓存(这个原理我是不清楚的哈,可以肯定的是, abcd 的指针指向的都是同一个值),abcd 都是 10。

  2. 关闭打印,a 的值被修改为 10, 未逃逸。 外部打印时,所有的变量都逃逸到 heap,abcd 都是 1(未被修改)。

逃逸分析:go run -gcflags='-m' main.go

咳咳,该点赞了哈,骚年~

1周前 评论
guoliang1994 (作者) 1周前
don178 (楼主) 1周前

应该说是,当值类型的变量发生逃逸时,会在堆上分配内存,数字类型的逃逸到堆上时,会调用convT64函数,在堆上分配一块内存,而go语言对于<=255数值的做了缓存,所以两个变量的值一样时,是在分配的同一块内存,然后你修改一个,另一个也会跟着变化

1周前 评论
don178 (楼主) 5天前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!