关联枚举的内存布局(一)

·  阅读 1229

从一个小例子引出本文

enum A {
    case a0(a:Int8) //这里是Int8类型
    case a1(a:Int8,b:Int,c:Int16)
}
print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
复制代码

enum A {
    case a0(a:Int) //这里是Int类型,在64位系统下占8字节
    case a1(a:Int8,b:Int,c:Int16)
}
print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
复制代码

一个是18一个是19,这里差的1是因为枚举的case也需要一个字节来存储,那么什么情况下+1,什么情况下不+1呢????我们来探索一下

首先怎么确定关联枚举所占用的内存大小呢?

计算每一个case占用内存大小取其最大值,计算case占用内存大小的规则为:

前面n-1个参数所占字节的和要为第n个参数所占用字节或8字节的最小正整数倍

enum Test{
     case enum1(t1:Int)
     case enum2(t1:Int8,t2:Int16,t3:Int)
     case enum3(t1:Int8,t2:Int,t3:Int16,t4:Int32)
     case enum4(t1:Int8,t2:Int,t3:Int16,t4:Int,t5:Int8,t6:String)
}
复制代码

enum1为8字节不需要对齐 占用8

enum2占用(1,2,8)共11字节,因为1除以2无法整除则需要把1变为2占用则变为(2,2,8),又因为2+2除以8不能整除则需要补4即把2变为6占用为(2,6,8)

enum3占用(1,8,2,4)共15字节 因为1除以8无法整除需要补7 变为(8,8,2,4),此时8+8除以2整除故不变,继续往后看8+8+2无法整除4 需要补2 变为(8,8,4,4)

enum4特殊一点 (1,8,2,8,1,16) 因为1无法整除8故需要补7 (8,8,2,8,1,16) 因为8+8整除2故不变 (8,8,2,8,1,16) 因为8+8+2不能整除8 故补6 (8,8,8,8,1,16) 因为8+8+8+8整除1 故不变 (8,8,8,8,1,16) 因为8+8+8+8+1=33,这时用33和谁比较呢,我们知道系统都是8字节对齐的,扩充16字节势必会浪费空间,我们继续和8比较 需要补7 (8,8,8,8,8,16)

参考文章 Swift枚举关联值占用大小

我们将实际内容存储区域成为真实存储位,将由于内存对齐被多分配出来的存储空间暂且称为扩充位(这是一个临时概念,帮助理解内容),将所有case的扩充位拿出来取交集,如果结果不为空,那么系统就可以确定当前给你分配的内存里边肯定有你用不到的区域,这个区域就可以用来存储case,下面来验证一番

举例

enum A {
	//1
    case a0(a:Int8) 
    
    // 1+1=2
    case a1(a:Int8,b:Int8) 
    
    //1+2+8,1/2不为正整数->2+2+8 4/8不为正整数->2+6+8=16
    case a2(a:Int8,b:Int16,c:Int) 
    
    // 1+4+8 1/4不为正整数->4+4+8 8/8为正整数->4+4+8=16
    case a3(a:Int8,b:Int32,c:Int) 
}

//1-8字节只有一个Int8类型的值,是1字节对齐
//1-8字节内存布局 01 00 00 00 00 00 00 00
var a0 = A.a0(a: 1)

//1-8字节存储了两个Int8类型的值,是1字节对齐
//1-8字节内存布局 01 02 00 00 00 00 00 00
var a1 = A.a1(a: 1, b: 2)

//1-8字节存储了一个Int8类型的值一个Int16类型的值,是2字节对齐
//1-8字节内存布局 01 00 02 00 00 00 00 00
var a2 = A.a2(a: 1, b: 2,c: 3)

//1-8字节存储了一个Int8类型的值一个Int32类型的值,是4字节对齐
//1-8字节内存布局 01 00 00 00 02 00 00 00
var a3 = A.a3(a: 1, b: 2,c: 3)

print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
print("end")
复制代码

验证一下

那么从这里我们就可以知道枚举占用16字节,a0、a1、a2、a3的真实存储位扩充位

a0真实存储位:[1]

a0扩充位:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

a1真实存储位:[1,2]

a1扩充位:[3,4,5,6,7,8,9,10,11,12,13,14,15,16]

a2真实存储位:[1,3,4,9,10,11,12,13,14,15,16]

a2扩充位:[2,5,6,7,8]

a3真实存储位:[1,5,6,7,8,9,10,11,12,13,14,15,16]

a3扩充位:[2,3,4]

将所有扩充位取交集为空,那么枚举的case就需要另外开辟一个字节存储,也就需要+1,验证一下

enum A {
    case a0(a:Int8) //1字节
    case a1(a:Int8,b:Int8) //1+1=2字节
    case a2(a:Int8,b:Int16,c:Int) //8+8=16字节
    case a3(a:Int8,b:Int32,c:Int) //8+8=16字节
}

//size应该是16+1=17字节
print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
复制代码

我们如果将a3的第二个参数改成Int16类型

enum A {
	//1
    case a0(a:Int8) 
    
    // 1+1=2
    case a1(a:Int8,b:Int8) 
    
    //1+2+8,1/2不为正整数->2+2+8 4/8不为正整数->2+6+8=16
    case a2(a:Int8,b:Int16,c:Int) 
    
    //1+2+8,1/2不为正整数->2+2+8 4/8不为正整数->2+6+8=16
    case a3(a:Int8,b:Int16,c:Int) 
}

//1-8字节只有一个Int8类型的值,是1字节对齐
//1-8字节内存布局 01 00 00 00 00 00 00 00
var a0 = A.a0(a: 1)

//1-8字节存储了两个Int8类型的值,是1字节对齐
//1-8字节内存布局 01 02 00 00 00 00 00 00
var a1 = A.a1(a: 1, b: 2)

//1-8字节存储了一个Int8类型的值一个Int16类型的值,是2字节对齐
//1-8字节内存布局 01 00 02 00 00 00 00 00
var a2 = A.a2(a: 1, b: 2,c: 3)

//1-8字节存储了一个Int8类型的值一个Int32类型的值,是4字节对齐
//1-8字节内存布局 01 00 00 00 02 00 00 00
var a3 = A.a3(a: 1, b: 2,c: 3)

print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
print("end")

复制代码

a0、a1、a2、a3的真实存储位扩充位

a0真实存储位:[1]

a0扩充位:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

a1真实存储位:[1,2]

a1扩充位:[3,4,5,6,7,8,9,10,11,12,13,14,15,16]

a2真实存储位:[1,3,4,9,10,11,12,13,14,15,16]

a2扩充位:[2,5,6,7,8]

a3真实存储位:[1,3,4,9,10,11,12,13,14,15,16]

a3扩充位:[2,5,6,7,8]

将所有扩充位取交集结果为[5,6,7,8],那么枚举的case就不需要另外开辟一个字节存储,也就不需要+1,验证一下

enum A {
    case a0(a:Int8) //1字节
    case a1(a:Int8,b:Int8) //1+1=2字节
    case a2(a:Int8,b:Int16,c:Int) //8+8=16字节
    case a3(a:Int8,b:Int16,c:Int) //8+8=16字节
}

var a3 = A.a3(a: 1, b: 2, c: 3)
print(MemoryLayout<A>.size)
print(MemoryLayout<A>.stride)
print("end")
复制代码

经过观察,case会存储在扩充位交集的最后一位上,验证结束!!!

关联枚举的内存布局(二)

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改