一文带你读懂结构体内存分配

栏目: IT技术 · 发布时间: 4年前

内容简介:一个比较牛逼的博客,介绍了对此,我深感佩服。因为代码非常简单,简单到我根本看不懂!之所以这么做的原因是:

一个比较牛逼的博客,介绍了 如何优化字符串到字节数组的过程 ,避免了数据复制过程对程序性能的影响。

对此,我深感佩服。因为代码非常简单,简单到我根本看不懂!

package main
 
import (
    "fmt"
    "strings"
    "unsafe"
)
 
func str2bytes(s string) []byte {
    x := (*[2]uintptr)(unsafe.Pointer(&s))
    h := [3]uintptr{x[0], x[1], x[1]}
    return *(*[]byte)(unsafe.Pointer(&h))
}
 
func bytes2str(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}
 
func main() {
    s := strings.Repeat("abc", 3)
    b := str2bytes(s)
    s2 := bytes2str(b)
    fmt.Println(b, s2)
}
复制代码

之所以这么做的原因是: 从 ptype 输出的结构来看,string 可看做 [2]uintptr,而 []byte 则是 [3]uintptr,这便于我们编写代码,无需额外定义结构类型。如此,str2bytes 只需构建 [3]uintptr{ptr, len, len},而 bytes2str 更简单,直接转换指针类型,忽略掉 cap 即可。

关于 string[]byte 结构可以看下图,我直接复制过来了,大家可以看看上述表达的根据。

一文带你读懂结构体内存分配

打击

作者的判断呢,我是相信的。于是,我跃跃欲试的修改了代码,也想完成类似的不用复制变量、仅仅修改指针类型就可以的改变变量类型的过程。下面的代码主要想做的就是,通过修改指针从而完成变量由结构体 Num 到结构体 ReverseNum 的转变,代码内容如下:

type Num struct {
    name  int8
    value int8
}

type ReverseNum struct {
    value int8
    name  int8
}
func main() {
    n := Num{100, 10}
    z := (*[2]uintptr)(unsafe.Pointer(&n))
    h := [2]uintptr{z[1], z[0]}
    fmt.Println(*(*ReverseNum)(unsafe.Pointer(&h))) // print result is {0, 0}
}
复制代码

但是,结果并不如我所愿。因为打印的结果并不是 {10, 100} ,而是 {0, 0} 。我的自信心受到了挫折,这种转化到底是什么意思呢???

在反复思考没有结果之后,我在著名的 stackoverflow 贴出了我的疑问。然后就在我打算休息会儿的时候,就有人评论了,给予我深深的打击。

对于我的这种写法,人家列出了七点看法。在我缓了缓挫败的内心之后再看的时候,被人删除了六点,唯一剩下的一点就是因为 unsafe 不够安全。总的来说我就是在我对 go 不够熟悉的时候,不要接触或者使用 unsafe 包。我就在想,什么知识不都是从不熟悉到熟悉的?我就是不够熟悉所以才会在 stackoverflow 上提问,也就是不熟悉才想熟悉这个知识点并且尝试熟悉这个知识点的啊!

unsafe 确实不安全,但是并不妨碍我了解这个包啊。

然后我就放弃了,毕竟评论的都是大佬,我这种渣渣也许就真的不适合知道这种知识。

转机

机缘巧合之下,我又看到了一篇博客,是 介绍内存对齐的 。其实之前也是看过内存对齐的文章,只不过仅仅是了解下。这篇文章让我想起了之前的疑问,所以我就带着疑问来反复读的这篇博客。

得到的知识和之前看内存对齐的博客是一致的,只不过这次我有了新的感悟。结构体的内存分配肯定是和内存对齐相关的。为了得到内存对齐的展示效果,这次没有使用两个都是 int8 属性的结构体。而是使用了一个新的结构体 Student ,有两个属性,一个属性是 int8 ,另外一个是 int64

import (
	"fmt"
	"unsafe"
)

type Student struct {
	age    int8
	salary int64
}

type StudentReverse struct {
	salary int64
	age    int8
}

func main() {
	s := Student{18, 100000}
	x := (*[2]uintptr)(unsafe.Pointer(&s))
	fmt.Println("age is ", *(*int8)(unsafe.Pointer(&x[0])))// 18
}
复制代码

这样打印的结果就是我想要的了,和我在 Student 初始化的时候赋值一致。然后需要做的就是如何通过指针修改类型了,既然第一步做到了,那么第二步就简单了,根据大佬的博客照葫芦画瓢就好了。

tmp := [2]uintptr{x[1], x[0]}
	studentReverse := *(*StudentReverse)(unsafe.Pointer(&tmp))
	fmt.Println(studentReverse.salary, studentReverse.age)
复制代码

打印的结果和预期一致,新的 studentReverse 结构体变量就按照预期进行了结构体的变换。

但是这种做法没什么意义,因为 uintptr 其实就是一个通用的指针,在函数 str2bytes 中的用法比较trick,不仅仅把结构体 string 中的数组指针作为指针,还把底层数组的长度也作为了指针。而在把 Student 转化为 StudentReverse 的过程中,只不过是把 Student 中每个元素值复制了了一份,没有任何意义。

读者可以尝试下修改变量 s 的属性,看下 studentReverse 是否也对应的修改了

还剩下最后一个问题,为什么贴在 stackoverflow 的代码就没有成功的运行。还是因为内存对齐,这两个 int8 类型的变量,因为内存对齐,放到了一个64位的内存中去了(要看系统支持的位数,我的电脑是64位的)。为了验证正确性呢,可以看如下代码

import (
	"fmt"
	"unsafe"
)

type Test struct {
	a int8
	b int8
}

func main() {
	test := Test{2, 3}
	z := (*[2]int8)(unsafe.Pointer(&test))
	fmt.Println("z is ", z)//z is  &[2 3]
	fmt.Printf("totally as one result is %b\n", *(*int16)(unsafe.Pointer(&test)))//totally as one result is 1100000010
}

复制代码

代码运行的结果 z 中,就是一个长度为2的数组指针,包含有两个值,一个是2(也就是t.a的值),一个是3(也就是t.b的值)。如果把结构体转化为一个 int16 的变量并按照二进制进行打印,结果是 1100000010 ,如果看的仔细的话,就知道后八位是2,前两位是3。

总的来说就是每个结构体地址后面有一段的内存空间,用户存放此结构体的属性。所以就有了 unsafe 包可以操作地址,操作 (*[2]int8)(unsafe.Pointer(&test)) 就是把变量 test 的地址之后的16位转化为了长度为2的元素类型为 int8 的数组,这样就可以直接通过操作指针的方式来操作内存。

但是呢,这些属性因为内存对齐,并不是一个一个紧凑并且连续排列的。而内存对齐在不同的操作系统或者不同的硬件上的要求也是各不相同。为了避免例如你在这个64位系统可以正常运行的操作,到了32位系统就崩溃了,所以 go 就极其不建议使用 unsafe 包。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

共鸣:内容运营方法论

共鸣:内容运营方法论

舒扬 / 机械工业出版社 / 2017-5-8 / 59.00

近5年来网络信息量增长了近10倍,信息极度过剩。移动互联网以碎片化、强黏度以及惊人的覆盖率给传统的商业环境带来了巨大的影响,向陈旧的广告、公关、媒体行业展开了深度的冲击。 传统的以渠道为中心的传播思想几近失效,优秀内容成为了各行业最稀缺的资产,这是时代赋予内容生产者的巨大机会。本书作者在多年经验和大量案例研究的基础上,总结出了移动互联网时代的内容运营方法论——共鸣,它将告诉我们如何收获核心粉......一起来看看 《共鸣:内容运营方法论》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具