问 fun1 和 fun2 fun3分别输出什么,为什么?
```go
func fun1() {
a := 2
c := (*string) (unsafe.Pointer(&a))
*c = "44"
fmt.Println(*c)
}
func fun2() {
a := "654"
c := (*string) (unsafe.Pointer(&a))
*c = "44"
fmt.Println(*c)
}
func fun3() {
a := 3
c := *(*string) (unsafe.Pointer(&a))
c = "445"
fmt.Println(c)
}
```
简单来说吧,变量a被定义时,其内存是在堆上分配了8个字节,因为a是int类型,占用8个字节
我们假设a的地址是0xc042012320,也就是这8个字节内存的首地址
变量b被定义时,发生同样的事情,b的地址是0xc042012328,长度也是8个字节
那么我们看一下当前堆的情况
0xc042012320----0xc04201232f 这16个字节的内存分别是a和b
当我们把a的指针unsafe强转为字符串指针c时,c这个指针的值仍然是0xc042012320,也就是它指向了跟a相同的内存空间
但是字符串对象在Go的底层其实是一个结构体
type StringHeader struct {
Data uintptr
Len int
}
这个结构体包含两个成员变量,Data和Len,它们各自长8个字节,所以整个结构体长16个字节
因为这个c跟a内存首地址相同,但是c长16个字节,而a只有8个字节,那么c所指向的结构体就有一部分溢出了a的内存之外
再精确一点描述,c.Data 的地址也是 0xc042012320,因为它是c中的第一个成员变量
c.Len 的地址就是0xc042012328,因为Data长度是8字节,所以Len的地址就是Data的地址向后偏移8个字节
那么现在c.Len 的地址刚好跟b的地址是一样的
简单描述,就是c.Len溢出到了b的内存里,这个就是我们常说的内存溢出,缓冲区溢出之类的。
如果我们不定义变量b,那么变 0xc042012328 之后的内存就是未分配的,这个时候c就溢出到了未分配的内存里了。
这种情况下对c.Len 读取读的就是未分配内存,所以值是随机的。而对c.Len写入的话,写入本身是不会报错的,但是会导致两种情况:
1. 0xc042012328 之后的内存一直未被分配。 这种情况代码通常可以正常运行,不会出错。因为堆的大小还是比较大的,远远超出几个变量的空间,所以这块内存虽然未分配,但是是有效的可以读写的。
2. 0xc042012328 之后的内存被分配给了其他变量。这种情况就比较复杂了,会有两个类型甚至都可能不同的变量对同一块内存进行读写,出现各种奇怪的情况。原始的例子fun1会panic就是因为这个原因。
***
***
当然上面的例子里,有个引申问题,结构体的内存对齐,不过这个问题跟当前讨论内容无关,就不细说了
#43
更多评论
```go
func fun1() {
a := 2
c := (*string) (unsafe.Pointer(&a)) //c为a的unsafe.pointer强制转为string指针,*c指的是a的值,a的值是int型的,这里*c = "44"是强制赋了个字符串,所以赋值是失败的,只是没有提示,因为使用了unsafe
*c = "44"
fmt.Println(*c) //这时的c是string指针,*c指的应该是一个字符串,但是里面实际上是int数据,所以操作也是失败的
}
func fun2() {
a := "654"
c := (*string) (unsafe.Pointer(&a)) //根据fun1的解释 a 为字符串型,那整个流程就通了,自然*c打出来的是 44,在*c="44"上面打印出来的是肯定是654
*c = "44"
fmt.Println(*c)
}
func fun3() {
a := 3
c := *(*string) (unsafe.Pointer(&a)) //这里获取的是a地址里的string值,但a的值是int型,所以操作失败,没有得到值,但c被定义里了string型变量,c = "445",给string型变量赋string值 自然是正常的
c = "445"
fmt.Println(c)
}
```
#1