Golang系列 - 内存对齐

Golang系列-内存对齐

摘要 : 本文将围绕内存对齐展开, 包括字符串、数组、切片等类型header的size大小、内存对齐、空结构体类型的对齐等等内容.
关键词 : Golang, 内存对齐, 字符串, 数组, 切片

常见类型header的size大小

首先看下面程序的输出, 对于字符串、数组、切片这三种类型, 通过unsafe.SizeOf 获取其在内存中占用的字节数.

go 复制代码
package main

import (
	"fmt"
	"unsafe"
)

func main() {
    str1 := ""
    str2 := "Hello, World!"
    arr1 := [0]int{}
    arr2 := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    slice1 := []int{}
    slice2 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Printf("str1: %d\n", unsafe.Sizeof(str1))
    fmt.Printf("str2: %d\n", unsafe.Sizeof(str2))
    fmt.Printf("arr1: %d\n", unsafe.Sizeof(arr1))
    fmt.Printf("arr2: %d\n", unsafe.Sizeof(arr2))
    fmt.Printf("slice1: %d\n", unsafe.Sizeof(slice1))
    fmt.Printf("slice2: %d\n", unsafe.Sizeof(slice2))
}

在64位机器下输出如下:

首先来说数组, 由于数组的大小是确定的, 所以其占用的内存字节书就是等于其长度 乘以 数据类型的大小, 在64位机器上, int占用8个字节, 所以空数组和长度为10的数组分别占用0和80个字节. 而字符串和切片的长度是不确定的, 在golang语言中, 其分别用如下的结构体来表示:

go 复制代码
type stringHeader struct {
    Data uintptr
    Len  int
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

uintptr 在64位机器下也是占用8个字节, 所以字符串和切片在内存中占用的字节数是固定的, 分别为16和24.

内存对齐

实际上golang语言中也存在和C语言类似的内存对齐, 其目的有如下几点:

  • 性能优化: 结构体中的每一个成员的首地址都是自己类型大小的整数倍, 这样保证了只需要通过一次内存操作就能够取出这个数
  • 原子操作: 同时, 如果能够在一次内存操作中完成一个数据的读出, 也能保证操作的原子性
  • 平台原因: 部分平台限制任意地址的读取
    内存对齐大致上有两个要求, 一个是每一个数据成员的起始地址必须是该数据成员字节长度的整数倍, 另外一个是整个结构体的大小是这个结构体对齐长度的整数陪, 结构体对齐长度为这个结构体中最大的数据类型的字节长度于机器位长的最小值.

空结构体类型

如果在一个结构体中有一个数据成员的类型是空结构体呢, 那么这个会给内存对齐带来什么影响? 先看如下程序运行的结果:

go 复制代码
package main

import (
	"fmt"
	"unsafe"
)

type DemoStruct1 struct {
    A int32
    D struct{}
}

type DemoStruct2 struct {
    D struct{}
    A int32
}

func main() {
    a1 := DemoStruct1{}
    a2 := DemoStruct2{}
    fmt.Printf("a1: size=%d, structAddr: %d, AAddr: %d, emptyStructAddr: %d\n", unsafe.Sizeof(a1), unsafe.Pointer(&a1), unsafe.Pointer(&a1.A), unsafe.Pointer(&a1.D))
    fmt.Printf("a2: size=%d, structAddr: %d, AAddr: %d, emptyStructAddr: %d\n", unsafe.Sizeof(a2), unsafe.Pointer(&a2), unsafe.Pointer(&a2.A), unsafe.Pointer(&a2.D))
}

在64位机器上输出如下:

可以看到, 如果空结构体数据成员是最后一个数据成员, 那么其会占用一个字节, 如果不是最后一个数据成员, 那么不会占用字节. 其原因是出于内存泄漏的考虑, 如果空结构体数据成员是结构体最后一个数据成员的时候, 如果其不占用字节, 那么其指针就会指向结构体外, 如果这个指针不被释放, 那么那一块内存就不会被释放, 而通过占用字节, 那么空结构体指针也会指向结构体内部, 避免内存泄漏的问题.

参考

相关推荐
小小张说故事2 分钟前
BeautifulSoup:Python网页解析的优雅利器
后端·爬虫·python
墨雪不会编程3 分钟前
C++之【深入理解Vector】三部曲最终章
开发语言·c++
怒放吧德德3 分钟前
后端 Mock 实战:Spring Boot 3 实现入站 & 出站接口模拟
java·后端·设计
浅念-10 分钟前
C语言编译与链接全流程:从源码到可执行程序的幕后之旅
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
biyezuopinvip15 分钟前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
UrbanJazzerati16 分钟前
Python编程基础:类(class)和构造函数
后端·面试
小宋102120 分钟前
Java 项目结构 vs Python 项目结构:如何快速搭一个可跑项目
java·开发语言·python
楚兴33 分钟前
MacBook M1 安装 OpenClaw 完整指南
人工智能·后端
一晌小贪欢1 小时前
Python 爬虫进阶:如何利用反射机制破解常见反爬策略
开发语言·爬虫·python·python爬虫·数据爬虫·爬虫python
Java编程爱好者1 小时前
2026版Java面试八股文总结(春招+秋招+社招),建议收藏。
后端