1.为什么需要内存对齐

1.1 提升CPU访问内存的效率

CPU访问内存通常是按照机器固定长度进行读取,如4字节,8字节,如果不按照固定长度读取到寄存器,那么就存在各种不必要的规则进行取舍和更多内存访问次数

1.2 增加程序的可移植性

不同的硬件平台,对于内存数据地址的读取,存在不一样的规则限制:有的可以任意访问任意地址的数据,有的只能在特定地址上存取特定类型数据,硬件平台的不一样.所以在编译器阶段解决这个问题就显得更加重要.

2.内存对齐规则

2.1 对齐系数和有效对齐值

对齐系数:在不同平台上,不同编译器拥有默认的对齐系数,例如32位系统,gcc中默认#pragma pack(4),也可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。

2.2 内存对齐的规则

规则1:结构体第一个成员的偏移量为0,以后每个成员的偏移量offset=min(成员大小,有效对齐值)*N

规则2:最终的结构体所占内存=有效对齐值*N (N为整数)

3.演示代码和成员位置

3.1 GO中两个辅助我们了解内存布局的函数

unsafe.Alignof 返回对应类型所需要的 有效对齐值

unsafe.Offsetof 函数的参数必须是一个字段 x.f,然后返回 f 字段相对于 x 起始地址的偏移量,包括可能的空洞。

package main

import (
    "fmt"
    "unsafe"
)

type A struct {
    a int8
    b int16
    c int8
}

type B struct {
    a int8
    b int8
    c int16
}

type C struct {
    a int8
    b int16
    c struct{}
}

func main() {
    a := A{1, 2, 3}
    b := B{1, 2, 3}
    c := C{}
    fmt.Println(unsafe.Sizeof(a), unsafe.Alignof(a), unsafe.Offsetof(a.b))                                             // 6 2 2
    fmt.Println(unsafe.Sizeof(b), unsafe.Alignof(b), unsafe.Offsetof(b.b))                                             // 4 2 1
    fmt.Println(unsafe.Sizeof(c), unsafe.Alignof(c), unsafe.Offsetof(c.a), unsafe.Offsetof(c.b), unsafe.Offsetof(c.c)) // 6 2 0 2 4
        fmt.Println(unsafe.Sizeof(c.c)) //0
}

file

空结构体作为结构体的内置字段:当变量位于结构体的前面和中间时,不占用内存;当该变量位于结构体的末尾位置时,需要进行内存对齐,内存占用大小和前一个变量的大小保持一致。偏移地址有点怪异,此处不解.......

4.场景利用

节约内存应用
和C/C++在cgo配合使用时,需要留意.

参考内容:
unsafe.Sizeof, Alignof 和 Offsetof · Go语言圣经 (studygolang.com)

1 对 “再论GO语言中的内存对齐”的想法;

发表回复