详解Golang中的for range循环(1.22及之后的版本)

1.22版本的for range

我们都知道,自从go1.22版本以来,for range允许查看元素地址,但是这个地址只是临时变量的地址,我们以下面的代码为例:

func main() {
    arr := [2]int{1, 2}
    res := []*int{}

    for i, v := range arr {
        fmt.Println(i, &v)
        res = append(res, &v)
    }
    fmt.Println(*res[0], *res[1])
}

从C语言出发

如果是C语言我们可以转化为以下代码:

for(int i = 0; i < 2; i++){
    v = arr[i];
    printf("%p\n",&v);
}

我们每次执行fmt.Println(&v)的时候只会输出一个地址,因为我们从始至终都只有一个变量v,不断承担着拷贝arr[i]的任务,因此输出自然只有一个,类似于早期go版本每次fmt.Println(i, &v)输出结果只有一个地址,因此最终fmt.Println(*res[0], *res[1])输出自然只能是同一个数,并且是最后赋值给v的数2。

回归Go语言(以下内容仅针对1.22版本之后)

经过测试,我们发现1.22版本居然可以正确输出结果1和2,并不是早期版本的错误输出2,2:

而且这个地址中间为什么隔着32字节?(0x20为16进制数,转化为32十进制数)

其实不难发现,我们的代码中存在一个append操作,有可能在对切片扩容的过程中转移了切片在内存中的位置,为此,我们修改代码进一步分析:

func main() {	
    arr := [2]int{1, 2}
	res := []*int{}

	for i, v := range arr {
		fmt.Println(i, &v)
		res = append(res, &v)
	}
	fmt.Println(*res[0], *res[1], unsafe.Sizeof(*res[0]), unsafe.Sizeof(res[1]))
	fmt.Println("res[0]is:", res[0])
	fmt.Println("res[1]is:", res[1])
	fmt.Println("The address of arr[0]is:", &arr[0])
	fmt.Println("The address of arr[1]is:", &arr[1])
}

我们首先输出了res每个元素(也就是地址)指向元素的长度,然后输出res数组中每个元素(地址)的长度,发现都为8字节(分别为int类型和*int类型),而后输出原数组arr地址。

结果我们发现res数组保存的1和2中间间隔48字节,并且arr[0]和arr[1]包含在这48字节中,因此,为了更进一步分析,我们将深入探索res[0]和res[1]中间包含的内容,由于GoLand中并没有类似VS一样查看内存数据的功能,并且golang不支持直接对指针进行加减乘除,因此我们只能使用unsafe库,将res[0]到res[1]中间的内容按照int(每8字节输出)。

【注】详细转换过程如下:

首先将地址经过unsafe.Pointer转化为Pointer类型,然后再转化为uintptr,这样就能进行相加减,计算完之后我们再将其强制转化为指向*int的指针即可。

	for i := 0; i < 7; i++ {
		//fmt.Println(*(uintptr(unsafe.Pointer(res[0])) + uintptr(i*8)))
		addr := uintptr(unsafe.Pointer(res[0])) + uintptr(i*8) // 将 i*8 转换为 uintptr
		fmt.Printf("Address at offset %d: %x\n", i*8, addr)
	
		// 通过解引用来获取这个地址的值
		value := *(*int)(unsafe.Pointer(addr))
		fmt.Printf("Value at address %x: %d\n", addr, value)
	}

最终我们得到:

 结果我们发现依然有3个地址存在问题,目前推测可能是以下问题:

  1. 实际数据不一定是按照8字节(即int)输出
  2. 这里面的数据有其他意义

等以后有时间了继续查找原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值