1 字节对齐
关联值字节对齐规则:前面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)
2 统一一些概念
扩充位,真实存储位
case enum1(t1:Int8,t2:Int16) //1 2 ==> 2 2
拿这个例子来讲, 由3个字节扩充到了4个字节,第一个字节真实存储Int8,第二个字节为扩充字节,第三四个字节真实存储Int16
无效位
假如我分配8字节存储一个字节的值,那么2-8字节为无效位
3 枚举关联值实际大小探究
正常情况下枚举占用大小,各项case字节对齐后的最大值+1字节(存储case类型) ————特殊情况,当对齐后最大项占用大小发生变化了,swift会优化,尝试将case类型存储到被扩充位的最后一位,如果这个位置也在其他case的扩充位或无效位 那么优化完成.占用大小即最大项占用大小
来看几个例子 注意x/8g 和 memory read打印端序不同 最好用Xcode自带view Memory of “value” 查看内存分布
例1
enum Test{
case enum1(t1:Int)
case enum2(t1:Int8,t2:Int,t3:Int16)
}
先对各项进行对齐
enum1 8 ==> 8
enum2 1,8,2 ==> 8,8,2 ==> 8,8,2
最大enum2占用18字节,由于enum2第一项t1被扩充为8字节,为了不浪费扩充空间,则系统尝试用第8字 节存储类型
enum1第八字节参与存储Int 并不在enum1的无效位或扩充位, 所以无法优化 从而得出占用19 第19字节存储类型
var b = Test.enum2(t1: 1, t2: 2, t3: 3)
我们构造b查看b的内存
01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
03 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00
按照 1 7 8 2 1 读取
01 为 t1 = 1
00 00 00 00 00 00 00 为扩充的无效位
00 00 00 00 00 00 00 02 为 t2 = 2
00 03 为 t3 = 3
01 为 case的类型
例2
enum Test{
case enum1(t1:Int16)
case enum2(t1:Int8,t2:Int,t3:Int16)
}
先对各项进行对齐
2 ==> 2
1,8,2 ==> 8,8,2 ==> 8,8,2
得出最大enum2为18 由于enum2第一项被扩充为8 则系统尝试将第8字节用于存储类型
判断第8字节是否在其他case的无效位或扩充位 发现enum1真实只占2个字节 则第8位为其无效位
得出可以将类型存储到这里 占用18 第8字节存储类型
var b = Test.enum2(t1: 1, t2: 2, t3: 3)
我们构造b查看b的内存
01 00 00 00 00 00 00 80 02 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
按照 7 1 8 2 读取
00 00 00 00 00 00 01 为 t1 = 1
80 为 case的类型
00 00 00 00 00 00 00 02 为 t2 = 2
00 03 为 t3 = 3
例3
enum Test{
case enum1(t1:Int16,t2:Int)
case enum2(t1:Int8,t2:Int,t3:Int16)
}
先对各项进行对齐
2,8 ==> 8,8
1,8,2 ==> 8,8,2 ==> 8,8,2
得出最大enum2为18 由于enum2第一项被扩充 则系统尝试将第8字节存储类型
判断第8字节是否在其他case的无效位或扩充位 发现enum1的第3--第8字节为扩充位
得出可以将类型存储到这里 占用18 第8字节存储类型
var b = Test.enum2(t1: 1, t2: 2, t3: 3)
我们构造b查看b的内存
01 00 00 00 00 00 00 80 02 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
按照 7 1 8 2 读取
00 00 00 00 00 00 01 为 t1 = 1
80 为 case的类型
00 00 00 00 00 00 00 02 为 t2 = 2
00 03 为 t3 = 3
以上我们都印证了 这个结论
正常情况下有关联值的枚举占用大小为各项case字节对齐后的最大值+1字节(存储case类型) ————特殊情况,当最大项因为对齐占用大小发生变化了,swift便会优化,尝试将case类型存储到被扩充位的最后一位,如果这个位置也在其他case的扩充位或无效位 那么优化完成.占用大小即最大项占用大小
再来看一个例子
例4
enum Test{
case enum1(t1:Int,t2:Int)
case enum2(t1:Int8,t2:Int,t3:Int16,t4:Int)
}
先对各项进行对齐
8,8 ==> 8,8
1,8,2,8 ==> 8,8,2,8 ==> 8,8,8,8
得出最大enum2为32 由于enum2第一项t1被扩充 则系统尝试将第8字节存储类型
判断第8字节是否在其他case的无效位或扩充位 发现enum1的第8字节用来存储t1 不合适
继续拿到enum2第二个扩充项t3 则系统尝试将第24字节存储类型
判断第24字节是否在其他case的无效位或扩充位 发现enum1的第24位为无用字节 合适 则 占用32 第24字节存储类型
var b = Test.enum2(t1: 1, t2: 2, t3: 3, t4: 4)
我们构造b查看b的内存
01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
03 00 00 00 00 00 00 80 04 00 00 00 00 00 00 00
我们按照 8 8 7 1 8读取
00 00 00 00 00 00 00 01 为t1 = 1
00 00 00 00 00 00 00 02 为t2 = 2
00 00 00 00 00 00 03 为t3 = 3
80 为case的类型
00 00 00 00 00 00 00 04 为t4 = 4
按照我们的推理,我们看到如果最大项字节对齐发生了扩充,系统就会尝试把1字节case类型尝试填充到扩充的末尾去.以此来节省资源
如果关联枚举再复杂一些呢?
例5
enum Test{
case enum1(t1:Int,t2:Int8,t3:Int,t4:String)
case enum2(t1:Int8,t2:Int,t3:Int16,t4:Int,t5:Int8,t6:String)
}
先对各项进行对齐
8,1,8,16 ==> 8,8,8,16
1,8,2,8,1,16 ==> 8,8,2,8,1,16 ==> 8,8,8,8,1,16 ==> 8,8,8,8,8,16
得出最大enum2为56 由于enum2第一项t1被扩充 则系统尝试将第8字节存储类型
判断第8字节是否在其他case的无效位或扩充位 发现enum1的第8字节用来存储t1 不合适
继续拿到enum2第二个扩充项t3 则系统尝试将第24字节存储类型
判断第32字节是否在其他case的无效位或扩充位 发现enum1的第17-24字节存储t3 不合适
继续拿到enum2第三个扩充项t5 则系统尝试将第40字节存储类型
判断第40字节是否在其他case的无效位或扩充位 发现enum1的第40字节参与存储t4 不合适
则 占用56+1=57字节 第57字节存储类型
var b = Test.enum2(t1: 1, t2: 2, t3: 3, t4: 4,t5: 5,t6: "1")
我们构造b查看b的内存
01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
00 00 00 00 00 00 00 E1 01 00 00 00 00 00 00 00
我们按照 8 8 8 8 8 16 1读取
00 00 00 00 00 00 00 01 为t1 = 1
00 00 00 00 00 00 00 02 为t2 = 2
00 00 00 00 00 00 00 03 为t3 = 3
00 00 00 00 00 00 00 04 为t4 = 4
00 00 00 00 00 00 00 05 为t5 = 5
31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E1 存储t6 = “1”
01 为case的类型