内存对齐及填充规则
内存对齐及填充规则
各类型占内存大小
| 数据类型 | 内存大小(byte) | 
|---|---|
| int | 8 | 
| int8 | 1 | 
| int16 | 2 | 
| int32 | 4 | 
| int64 | 8 | 
| string | 16 | 
| bool | 1 | 
| [3]int32 | 12 | 
| [3]int64 | 24 | 
| []string | 24 | 
| map[string]bool | 8 | 
| map[string]int | 8 | 
| map[string]string | 8 | 
| map[int]bool | 8 | 
| map[int]int | 8 | 
| map[int]string | 8 | 
| struct{} | 0 | 
代码示例:
func main() {
	var a int
	var b int8
	var c int16
	var d int32
	var e int64
	var f string
	var g bool
	var h [3]int32
	var i [3]int64
	var j []string
	var k map[string]bool
	var l map[string]int
	var m map[string]string
	var n map[int]bool
	var o map[int]int
	var p map[int]string
	var q struct{}
	fmt.Printf("int size:%v\n", unsafe.Sizeof(a))               // int size:8
	fmt.Printf("int8 size:%v\n", unsafe.Sizeof(b))              // int8 size:1
	fmt.Printf("int16 size:%v\n", unsafe.Sizeof(c))             // int16 size:2
	fmt.Printf("int32 size:%v\n", unsafe.Sizeof(d))             // int32 size:4
	fmt.Printf("int64 size:%v\n", unsafe.Sizeof(e))             // int64 size:8
	fmt.Printf("string size:%v\n", unsafe.Sizeof(f))            // string size:16
	fmt.Printf("bool size:%v\n", unsafe.Sizeof(g))              // bool size:1
	fmt.Printf("[3]int32 size:%v\n", unsafe.Sizeof(h))          // [3]int32 size:12
	fmt.Printf("[3]int64 size:%v\n", unsafe.Sizeof(i))          // [3]int64 size:24
	fmt.Printf("[]string size:%v\n", unsafe.Sizeof(j))          // []string size:24
	fmt.Printf("map[string]bool size:%v\n", unsafe.Sizeof(k))   // map[string]bool size:8
	fmt.Printf("map[string]int size:%v\n", unsafe.Sizeof(l))    // map[string]int size:8
	fmt.Printf("map[string]string size:%v\n", unsafe.Sizeof(m)) // map[string]string size:8
	fmt.Printf("map[int]bool size:%v\n", unsafe.Sizeof(n))      // map[int]bool size:8
	fmt.Printf("map[int]int size:%v\n", unsafe.Sizeof(o))       // map[int]int size:8
	fmt.Printf("map[int]string size:%v\n", unsafe.Sizeof(p))    // map[int]string size:8
	fmt.Printf("struct{} size:%v\n", unsafe.Sizeof(q))          // struct{} size:0
}
内存对齐和填充规则
- 对齐要求:每个数据类型的起始地址必须是其大小的倍数。
int8(1字节):不需要对齐。int16(2字节):起始地址必须是2的倍数。int32(4字节):起始地址必须是4的倍数。int64(8字节):起始地址必须是8的倍数。
 - 填充规则:如果当前偏移量不是下一个成员变量对齐要求的倍数,则编译器会在前一个成员后插入“填充字节”,以使下一个成员的起始地址满足对齐要求。
 - 结构体总大小:结构体的总大小必须是其最大成员对齐大小的倍数,必要时会在结构体末尾添加额外的填充字节。
 
示例解析
示例 1:未优化的结构体
type Unoptimized struct {
    a int8   // 1 byte
    b int32  // 4 bytes, 需要4字节对齐
    c int16  // 2 bytes, 需要2字节对齐
}
- a 占用 1 字节,起始地址为 0。
 - b 需要 4 字节对齐,但 
a只占用了 1 字节,因此在a后面需要填充 3 字节,使得b的起始地址为 4。 - c 需要 2 字节对齐,
b占用 4 字节,所以c的起始地址为 8,不需要额外填充。 - 结构体总大小为 10 字节(1 + 3 + 4 + 2),但为了使结构体大小为 4 字节对齐(最大成员 
b是 4 字节对齐),需要在末尾再填充 2 字节。 
最终结构体大小为 12 字节。
示例 2:优化后的结构体
type Optimized struct {
    b int32  // 4 bytes, 需要4字节对齐
    c int16  // 2 bytes, 需要2字节对齐
    a int8   // 1 byte, 不需要对齐
}
- b 占用 4 字节,起始地址为 0,符合 4 字节对齐。
 - c 需要 2 字节对齐,
b占用 4 字节,所以c的起始地址为 4,不需要额外填充。 - a 占用 1 字节,
c占用 2 字节,所以a的起始地址为 6,不需要额外填充。 - 结构体总大小为 7 字节(4 + 2 + 1),但为了使结构体大小为 4 字节对齐(最大成员 
b是 4 字节对齐),需要在末尾再填充 1 字节。 
最终结构体大小为 8 字节。
图解填充规则
假设我们有一个结构体:
type Example struct {
    a int8   // 1 byte
    b int16  // 2 bytes
    c int32  // 4 bytes
}
我们可以用图来表示内存布局:
Offset: 0  1  2  3  4  5  6  7  8  9 10 11
        +--+--+--+--+--+--+--+--+--+--+--+--+
        | a| P| P| P| b| b| P| P| c| c| c| c|
        +--+--+--+--+--+--+--+--+--+--+--+--+
a占用 1 字节,后面填充 3 字节(P 表示填充字节)。b占用 2 字节,后面填充 2 字节。c占用 4 字节。
调整顺序后:
type Example struct {
    c int32  // 4 bytes
    b int16  // 2 bytes
    a int8   // 1 byte
}
内存布局变为:
Offset: 0  1  2  3  4  5  6  7
        +--+--+--+--+--+--+--+--+
        | c| c| c| c| b| b| a| P|
        +--+--+--+--+--+--+--+--+
c占用 4 字节。b占用 2 字节。a占用 1 字节,后面填充 1 字节。
最终结构体大小为 8 字节,比原来的 12 字节更紧凑。
总结
通过将占用较大内存空间的成员放在前面,可以减少编译器为了对齐而插入的填充字节数量,从而使结构体更加紧凑,节省内存。你可以使用 unsafe.Sizeof() 和 unsafe.Alignof() 来验证这些结构体的实际大小和对齐方式。
                    
                
                
            
        
浙公网安备 33010602011771号