golang 内存分析之字节对齐规则

    c 的字节对齐很重要,因为c 支持指针运算。在golang 里面一般是慎用指针运算的,所以,这部分很少用,但是有些场景为了性能不得不用到指针运算,这个时候,知道golang 的内存分配就很重要了。但是基本很少有相关的参考资料,很多也不靠谱,这里借鉴c 的规则验证golang 的内存对齐规则。

该文章后续仍在不断的更新修改中, 请移步到原文地址http://www.dmwan.cc/?p=154

    首先,有个问题,为什么 函数 unsafe.Offsetof(A.a1) 的参数怪怪的,非得把结构体类型也传进去?

    c中字节对齐规则:
    1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的地址必须是它本身大小或对齐参数两者中较小的一个的倍数。
    2、整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,整体长度必须是对齐参数和结构体最长的元素长度中较小的一个的倍数。

    参考规则我们看下面这些结构的数据分配,注:64位平台,对齐参数8.

package main
import (
"fmt"
"unsafe"
)
type A struct {
	a1 int8     // offset = 0
	a2 int16    // offset = 1 / min(8, sizeof(int16)=2 )=2
}
// 4 / min(8, 2) 大小为4

type B struct {
	b1 int16   // offset = 0
	b2 int8    // offset = 2 / min(8, 1) = 2
	b3 int	   // offset = 3 / min(8, 1) = 3
}
// [0, 1] + 2 + [3, 10]=11 / min(8, 8) 不尽,不符合规则2,大小为16,内存圆整

type C struct {
    c1 int8    // offset = 0
    c2 float32 // offset = 1 /min(8, 4) = 4
    c3 int     // offset = 8 / min(8, 8)=8
}
// 16/ min(8, 8)  大小为16

type D struct {
	d1 float32 // offset = 0
	d2 int     // offset = 4 / min(8, 8) = 8
	d3 int8    // offset = 16 / min(8, 1) = 16
}

// 17 /min(8, 8), 大小为17,数据圆整,Padding 为24 /8 = 3能整除


func main() {
	var a = A{}
	var b = B{}
	var c = C{}
	var d = D{}
	// size of A = 4
	// a1: 1 字节 + 1 字节padding
	// a2: 2 字节
    fmt.Printf("a.a1 offset %v \n", unsafe.Offsetof(a.a1))
    fmt.Printf("a.a2 offset %v \n", unsafe.Offsetof(a.a2))
	fmt.Printf("size of A = %d\n", unsafe.Sizeof(a))
	// size of B = 16
	// b1: 2 字节
	// b2: 1 字节 + 5 字节padding
	// b3: 8 字节
    fmt.Printf("b.b1 offset %v \n", unsafe.Offsetof(b.b1))
    fmt.Printf("b.b2 offset %v \n", unsafe.Offsetof(b.b2))
    fmt.Printf("b.b3 offset %v \n", unsafe.Offsetof(b.b3))
	fmt.Printf("size of B = %d\n", unsafe.Sizeof(b))
	// size of c = 16
	// c1: 1 字节 + 3 字节padding
	// c2: 4 字节
	// c3: 8 字节
    fmt.Printf("c.c1 offset %v \n", unsafe.Offsetof(c.c1))
    fmt.Printf("c.c2 offset %v \n", unsafe.Offsetof(c.c2))
    fmt.Printf("c.c3 offset %v \n", unsafe.Offsetof(c.c3))
	fmt.Printf("size of C = %d\n", unsafe.Sizeof(c))
	// size of d = 24
	// d1: 4字节 + 4字节padding
	// d2: 8 字节
	// d3: 1字节 + 7 字节padding
	// d1的尾部padding的原因是要保证是结构体自身也是对齐的
	// 因为这样可以确保实现结构体数组时候里面每个元素也是对齐的
    fmt.Printf("d.d1 offset %v \n", unsafe.Offsetof(d.d1))
    fmt.Printf("d.d2 offset %v \n", unsafe.Offsetof(d.d2))
    fmt.Printf("d.d3 offset %v \n", unsafe.Offsetof(d.d3))
	fmt.Printf("size of D = %d\n", unsafe.Sizeof(d))
	// 由于有补齐,两个结构体即便有相同类型的字段,但前后顺序不同也可导致size不同
}

    发现c 的内存分配规则其实和golang 是一致的。最后其实这里是不是一致对于使用来说不会有太大影响,因为指针计算,转换之前,会提前用offsetof 计算,这个偏移,golang 会将padding 都算进去, 和c 用的时候不太一样,这样做,不会出错。

    这里特别提下就是不同平台和对齐参数的不一致,会导致结果完全不同。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值