golang内存对齐

背景

在golang中,每一种数据类型都有其对应的数据类型大小,也就是占用了多少内存空间

我们可以通过unsafe.Sizeof函数,来确定一个变量占用的内存字节数

demo:

go 复制代码
package main

import (
   "fmt"
   "testing"
   "unsafe"
)

func TestTypeSize(t *testing.T) {
   var a int8 = 4
   s1 := "hello world"
   s2 := "hahaha"
   fmt.Println(unsafe.Sizeof(a))  // 1字节
   fmt.Println(unsafe.Sizeof(s1)) // 16字节
   fmt.Println(unsafe.Sizeof(s2)) // 16字节
}

注意:

  • unsafe.Sizeof返回的是一个变量占用的内存字节数,而不是变量所表示内容占用的内存字节数。所以在上述demo中,尽管s1和s2的字符串内容不一样,但s1和s2的变量类型都是string,所以s1和s2占用的内存字节数相同

结构体大小

我们还可以通过unsafe.Sizeof来获取结构体占用的内存字节数

demo1:

go 复制代码
package main

import (
   "fmt"
   "testing"
   "unsafe"
)

type demo1 struct {
   a int8
   b int16
}

func TestStructSize1(t *testing.T) {
   d1 := demo1{}
   fmt.Println(unsafe.Sizeof(d1.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d1.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d1))   // 4字节
}

问题1:

  • 结构体占用的内存字节数不等于结构体内各个字段占用的内存字节数之和。结构体内各个字段占用的内存字节数之和为:3字节 = 1字节(a占用字节数) + 2字节(b占用字节数),结构体占用字节数为4字节

demo2:

go 复制代码
package main

import (
   "fmt"
   "testing"
   "unsafe"
)

type demo2 struct {
   a int8
   b int16
   c int32
   d int64
}

type demo3 struct {
   a int8
   d int64
   b int16
   c int32
}

func TestStructSize2(t *testing.T) {
   d2 := demo2{}
   fmt.Println(unsafe.Sizeof(d2.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d2.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d2.c)) // 4字节
   fmt.Println(unsafe.Sizeof(d2.d)) // 8字节
   fmt.Println(unsafe.Sizeof(d2))   // 16字节

   d3 := demo3{}
   fmt.Println(unsafe.Sizeof(d3.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d3.b)) // 2字节
   fmt.Println(unsafe.Sizeof(d3.c)) // 4字节
   fmt.Println(unsafe.Sizeof(d3.d)) // 8字节
   fmt.Println(unsafe.Sizeof(d3))   // 24字节
}

问题2:

  • 当两个结构体内的字段类型一样时,字段顺序不同时,结构体占用的字节数也可能不同

出现上面两个问题的根本原因,就是本文要讨论的内容:内存对齐

什么是内存对齐

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐

简单来说,内存对齐就是把各种数据类型按照一定的规则,在内存空间进行排列,而不是直接按照顺序进行排列

为什么需要内存对齐

那么为什么需要进行内存对齐呢,主要原因有以下几点:

  • 性能原因:CPU为了加速对内存的访问速度,并不是一个字节一个字节的去访问内存,而是一次访问多个字节,一般称之为字长。32位CUP的字长一般是4字节,64位CPU的字长一般是8字节。如果没有进行内存对齐,那么CPU在访问一个变量时,可能需要进行多次读取,然后进行拼接,才能得到最终的变量内容。进行内存对齐后,可以减少CPU访问内存次数,提升性能
  • 更好的保证访问的原子性
  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

如何进行内存对齐

常见数据类型的内存对齐

编译器按照每种数据类型的对齐边界来进行内存对齐

首先需要确认每种数据类型的对齐边界,对齐边界 = min(类型大小,平台字长)

常见数据类型在常见平台上的对齐边界:

类型 类型大小 64位平台字长 64位平台对齐边界 32位平台字长 32位平台对齐边界
int8 1byte 8byte 1byte 4byte 1byte
int16 2byte 8byte 2byte 4byte 2byte
int32 4byte 8byte 4byte 4byte 4byte
int64 8byte 8byte 8byte 4byte 4byte
string 16byte 8byte 8byte 4byte 4byte
...

为什么对齐边界需要取类型大小和平台字长的最小值呢?

答案是为了节省内存,避免内存浪费,提升读取的性能

  1. 当类型大小 < 平台字长时,int8类型在64位平台进行内存对齐两种情况如图:
  1. 当类型大小 > 平台字长时,int64类型在32位平台进行内存对齐两种情况如图:

结构体的内存对齐

结构体的对齐边界为:结构体内成员类型最大的对齐边界

结构体进行内存对齐的两个要求:

  1. 起始地址是结构体对齐边界的倍数
  2. 结构体整体占用字节数必须是结构体对齐边界的倍数。为了保证结构体数组的内存对齐

下面我们具体分析下结构体的内存对齐

go 复制代码
type demo4 struct {
   a int8
   b int64
   c int32
   d int16
}

首先确定结构体demo4的对齐边界为:成员类型最大的对齐边界 = 8byte

结构体内存对齐如图:

结构体内存对齐的特殊情况

如果结构体的字段包含空结构体类型时

  • 空结构体类型字段不是最后一个字段时,不会占用内存
  • 空结构体类型字段是最后一个字段时,需要进行内存对齐,占用的内存大小和前一个字段的大小一致

demo:

go 复制代码
package main

import (
   "fmt"
   "testing"
   "unsafe"
)

type demo5 struct {
   s struct{}
   a int8
}

type demo6 struct {
   a int8
   s struct{}
}

func TestStructSize3(t *testing.T) {
   d5 := demo5{}
   fmt.Println(unsafe.Sizeof(d5.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d5.s)) // 0字节
   fmt.Println(unsafe.Sizeof(d5))   // 1字节

   d6 := demo6{}
   fmt.Println(unsafe.Sizeof(d6.a)) // 1字节
   fmt.Println(unsafe.Sizeof(d6.s)) // 0字节
   fmt.Println(unsafe.Sizeof(d6))   // 2字节
}

空结构体类型字段是最后一个字段时,需要占用内存,主要还是为了解决内存泄漏问题

内存泄漏问题分析:

相关推荐
moisture1 小时前
C++ 值类别、auto与decltype
后端·面试
极客先躯1 小时前
高级java每日一道面试题-2024年9月15日-架构篇[分布式篇]-如何在分布式系统中实现事务?
java·数据库·分布式·面试·架构·事务·分布式篇
bugtraq20212 小时前
Fyne ( go跨平台GUI )中文文档-绘图和动画(三)
开发语言·后端·golang
Adolf_19932 小时前
Flask-JWT-Extended登录验证
后端·python·flask
2402_857589362 小时前
基于Spring Boot的Java免税商品优选商城设计
java·spring boot·后端
Passion不晚4 小时前
Spring Boot 入门:解锁 Spring 全家桶
spring boot·后端·spring
yanlele4 小时前
前端面试第 66 期 - Vue 专题第二篇 - 2024.09.22 更新前端面试问题总结(20道题)
前端·javascript·面试
hn小菜鸡4 小时前
LeetCode 面试经典150题 67.二进制求和
算法·leetcode·面试
蜡笔小流4 小时前
Flask 第六课 -- 路由
后端·python·flask
江凡心4 小时前
Qt 每日面试题 -2
开发语言·数据库·qt·面试