接着上篇 从 简单汇编基础 到 Swift
的牛刀小试swift
本文来轻探一下Swift 枚举数组
虽不高级bash
但也称得上积极ide
待到枚举汇编后 ,犹是惊鸿初见时函数
既然决定了偷窥,天然要带好头盔⛑post
在 Swift3 中 MemoryLayout 就取代了 sizeof ,用来查看数据占用的内存大小
性能
有意思的是ui
MemoryLayout 也是一个枚举,其中包含的 有3个类型spa
size、stride、alignment,返回值 都是 Int,表明 字节个数
3d
简单使用:
let a = "string"
MemoryLayout.size(ofValue: a)
// a 的字节大小
MemoryLayout<Int>.size
// 查看某个类型的字节占用,这里Int 8个字节
复制代码
size
stride
alignment
以 买肥宅水 为例
你只能喝半瓶,半瓶就是size,取决你胃的实际大小
老板给你拿了一瓶,这里的一瓶 就是 stride,实际出售于你
瓶是一个对齐单位,由于无法 半瓶出售,alignment 就是 一瓶
复制代码
计算机的内存以 字节 为单位,可是读取的时候 并不以字节为 单位进行读取
,就像你吃饭 不以米粒
为单位
何为 内存对齐?
将内存数组单元 放在合适的位置,以计算机定义的
对齐单位
为准,通常以二、四、8 的倍数 为单位
何为合适?
数据分配字节,同一个数据 分配在一个 对齐单位内,视为合适
好友 4 人出去玩,安排在同一辆车上 ,视为合适
假若 是一辆摩托车呢?
每辆方能坐 2 我的,对齐单位视为 2人
假若 不坐车呢 ?
您是说步行吗 ?
:行啊,我没说不行啊,我行的很
……
复制代码
为什么 内存对齐?
提升系统内存的性能,若是内存读取以4个字节为读取粒度
进行读取
假若没有
内存对齐,数据即可 自由分配 起始位置等
某数据占用4个字节,偏移量从1开始,则内存就要读取2次
假若数据 从 偏移量 0开始对齐
,那么内存只需读取一次
做为一个食物人
不是植物人
能一口吃完的饭,毫不分做两口
简而言之:内存对齐是一种更好的存取方式
内存虽充够,严谨点总该是好的
明明 一辆车就能够载的下 4我的,何须 须要 2辆车呢?
明明 他有车
苦于我没有啊
用旁光瞥了半天
可算瞥到了枚举
首先定义一个 没有原始值,没有关联值的枚举
enum Apple {
case mac
case pad
case phone
}
func test() {
var a = Apple.mac // 赋值 第1次
a = .pad // 赋值 第2次
a = .phone // 赋值 第3次
<!--print("须要占用: \(MemoryLayout.size(ofValue: a)) 字节")-->
<!--print("实际分配: \(MemoryLayout.stride(ofValue: a)) 字节")-->
<!--print("内存对齐: \(MemoryLayout.alignment(ofValue: a)) 字节")-->
}
-> test()
// output : 都是1个字节
复制代码
实际汇编中,刚开始我建议仍是把代码写在函数中,以及不要 print
,能够省去不少 乱七八糟(我太菜看不懂
) 的汇编代码
run
, 断点打在test, 能够看到main 函数 的汇编代码
zzz`main:
0x100001420 <+0>: pushq %rbp // 压栈
0x100001421 <+1>: movq %rsp, %rbp // 栈顶rsp设置初始值,指向rbp
0x100001424 <+4>: subq $0x10, %rsp // 分配栈空间0x10 ,16个字节,栈地址向下生长,rsp向下移动
0x100001428 <+8>: movl %edi, -0x4(%rbp) // 参数,打印值edi 值为1,猜想和 分配字节数有关,望指点
0x10000142b <+11>: movq %rsi, -0x10(%rbp) // 参数,不知何义 望指点
-> 0x10000142f <+15>: callq 0x100001640 ; zzz.test() -> () at main.swift:210
0x100001434 <+20>: xorl %eax, %eax // eax 重置
0x100001436 <+22>: addq $0x10, %rsp // 回收栈空间
0x10000143a <+26>: popq %rbp // 指回上一层 rbp
0x10000143b <+27>: retq // 指向下一条命令
复制代码
敲下 si 进入 test内部
zzz`test():
-> 0x100001640 <+0>: pushq %rbp
0x100001641 <+1>: movq %rsp, %rbp
0x100001644 <+4>: movb $0x0, -0x8(%rbp)
0x100001648 <+8>: movb $0x1, -0x8(%rbp)
0x10000164c <+12>: movb $0x2, -0x8(%rbp)
0x100001650 <+16>: popq %rbp
0x100001651 <+17>: retq
复制代码
大概能够看到 比较重要2个信息
一. movb, b 表明一个字节,枚举值占用一个字节
二. 0 、一、2 三个数 分别赋值给同一内存空间,也就是说 mac,pad,phone 分别对应3个 数值
那咱们来打印一下 a 的内存地址,查看里面装了些什么
print(UnsafeRawPointer(&a))
打印a 的内存地址 0x00007ffeefbff4d8
// 分别3次 赋值的断点记录
(lldb) x/g 0x00007ffeefbff4d8
0x7ffeefbff4d8: 0x0000000000000000
(lldb) x/g 0x00007ffeefbff4d8
0x7ffeefbff4d8: 0x0000000000000001
(lldb) x/g 0x00007ffeefbff4d8
0x7ffeefbff4d8: 0x0000000000000002
复制代码
确实如咱们所想
a 占用一个字节,里面装的是 0 ,1 ,2 分别表明 mac,pad,phone
字节数
枚举类型
无原始值、无关联值
的 枚举的 占用字节 为 1case 的值
下标值
,从 0 开始
写下如此,给定初始值 100
enum Apple: Int {
case mac = 100
case pad
case phone
}
复制代码
一样进入 test 内部
zzz`test():
0x1000015b0 <+0>: pushq %rbp
0x1000015b1 <+1>: movq %rsp, %rbp
0x1000015b4 <+4>: movb $0x0, -0x8(%rbp)
0x1000015b8 <+8>: movb $0x1, -0x8(%rbp)
-> 0x1000015bc <+12>: movb $0x2, -0x8(%rbp)
0x1000015c4 <+20>: popq %rbp
0x1000015c5 <+21>: retq
复制代码
如何 ,看着是否眼熟 ?
再品一品
不曾见到 原始值 100
,此汇编代码和 第一探 如出一辙
由此,咱们也能够暂下 结论
原始值的枚举 并未将原始值 写入内存
存入的依旧是 能够理解为下标
的数值,从 0 开始
不论初始值为多少,都是从0 开始,依次累加
原始值
rawValue
去哪了 ?
那咱们试着调用一下 rawValue
看看 它的汇编代码
var a = Apple.mac
a.rawValue
复制代码
再次观察汇编
zzz`test():
0x100001540 <+0>: pushq %rbp
0x100001541 <+1>: movq %rsp, %rbp
0x100001544 <+4>: subq $0x10, %rsp
0x100001548 <+8>: movb $0x0, -0x8(%rbp)
0x10000154c <+12>: xorl %edi, %edi
0x10000154e <+14>: callq 0x100001310 ; zzz.Apple.rawValue.getter : Swift.String at <compiler-generated>
0x100001553 <+19>: movq %rdx, %rdi
复制代码
我扶了扶 600° 的眼镜
默念脍炙人口的法决
三长一短选最短
三短一长选最长
下面这句 可尤为的长啊
callq
0x100001310 ; zzz.Apple.rawValue.getter
这彷佛是一个 方法调用
callq & getter
:rawValue 的getter 方法 调用,取rawValue 的值
那么,因而可知
有原始值的枚举
,也是以 相似下标值的东西 存储,从 0 开始
,与原始值 无关
有原始值的枚举
也是占用 1个字节
有原始值的枚举
rawValue 是 以调用 getter
方法取值
看完前两种,接下来与 关联值枚举
会上一会
以下
enum Article {
case like(Int)
case collection(Bool)
case other
case sad
}
func test() {
var a = Article.like(20) // 步骤1
a = .collection(true) // 步骤2
a = .other // 步骤3
a = .sad // 步骤4
}
test()
/* output:
size 须要占用: 9 字节
stride 实际分配: 16 字节
alignment 内存对齐: 8 字节
/
复制代码
看了以上结果
Int 占用 8 个字节, Bool 占用 1个字节,other 占用一个字节
考虑到内存空间,能够复用
若是我猜的没错
8 + 1 + 1 - 2 = 8 个字节
这点算术还要猜 ?
拿计算机算啊
结论为 9个字节
结果和我所想并不一致,考虑到自身知识浅薄,难不成
是程序 出了问题 ?
打印出a 的内存地址,查看 步骤一、二、三、4 处
内存的值
由于 实际分配了 16个字节
因此就 x/2g
了,分红2份,1份 8个字节 ,足以展现所有
// 步骤1的值
(lldb) x/2g 0x00007ffeefbff4d0
0x7ffeefbff4d0: 0x0000000000000014 0x0000000000000000
// 步骤2的值
(lldb) x/2g 0x00007ffeefbff4d0
0x7ffeefbff4d0: 0x0000000000000001 0x0000000000000001
// 步骤3的值
(lldb) x/2g 0x00007ffeefbff4d0
0x7ffeefbff4d0: 0x0000000000000000 0x0000000000000002
// 步骤4的值
(lldb) x/2g 0x00007ffeefbff4d0
0x7ffeefbff4d0: 0x0000000000000001 0x0000000000000002
复制代码
根据打印,作一下概括
位置 | 前 8个 字节 | 后 8个 字节 |
---|---|---|
.like(20) | 关联值 20 | 0 |
.collection(true) | 1 | 1 |
.other | 0 | 2 |
.sad | 1 | 2 |
后 8 个字节
单看 .other 和 .sad , 前 8个字节依次为 0 和 1
,后 8个字节都是 2
这个 2
是共同之处
再 回头看 这 2个 case,是否类型一致 ?
咱们能够把
2
定为 它们的 共有 类型吗 ?
结合 .like 的 0
和 .collection 的 1
,好像确是如此
遂:
后8个字节 表 类型,区分 Bool,Int,以及无类型
前 8个字节
类型 | 前 8个字节 |
---|---|
Int | Int 值 |
Bool | 1表 true;0表 false |
无类型 | 依次累加,与 一探 相符 |
若此时再 结合汇编代码
0x100001794 <+4>: movq $0x14, -0x10(%rbp)
0x10000179c <+12>: movb $0x0, -0x8(%rbp)
-> 0x1000017a0 <+16>: movq $0x1, -0x10(%rbp)
0x1000017a8 <+24>: movb $0x1, -0x8(%rbp)
0x1000017ac <+28>: movq $0x0, -0x10(%rbp)
0x1000017b4 <+36>: movb $0x2, -0x8(%rbp)
0x1000017b8 <+40>: movq $0x1, -0x10(%rbp)
0x1000017c0 <+48>: movb $0x2, -0x8(%rbp)
复制代码
-0x10(%rbp) 这8个字节 分别 赋值 : 20、一、0、1
与前面分析一致
-0x8(%rbp) 这8个字节 分别赋值 :0、一、二、2
同一致
这个结果
阁下是否豁然开朗 ?
一直在说 后8个字节,其实咱们只须要看第9个字节
以下第9个字节为 0x01
(lldb) x/16b 0x00007ffeefbff4d0
0x7ffeefbff4d0: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7ffeefbff4d8: 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00
复制代码
第 9 个字节 便为 类型区分字节
针对关联值 本例:
关联值 枚举
最大字节数之和 额外 + 1
case 类型
非关联值
粗鄙之言 ,还望体谅
如如有误 ,还请指出
~