接着上篇 從 簡單彙編基礎 到 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 類型
非關聯值
粗鄙之言 ,還望體諒
如如有誤 ,還請指出
~