從 彙編 到 Swift 枚舉內存 的驚鴻一瞥

接着上篇 從 簡單彙編基礎 到 Swift 的牛刀小試swift

本文來輕探一下Swift 枚舉數組

雖不高級bash

但也稱得上積極ide

待到枚舉彙編後 ,猶是驚鴻初見時函數

MemoryLayout

既然決定了偷窺,天然要帶好頭盔⛑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

    • 實際分配的字節數
    • 實際分配數 stride 是 alignment 的整數倍
    • 使用同上
  • 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

總結

  • 字節數

    • 16進制:0x00 ~ 0xff ,表一個字節的範圍也就是 0 ~ 256
  • 枚舉類型

    • 無原始值、無關聯值的 枚舉的 佔用字節 爲 1
  • case 的值

    • 每一個 case 存儲的能夠 理解爲它對應的下標值從 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 的值

那麼,因而可知

總結

  1. 有原始值的枚舉 ,也是以 相似下標值的東西 存儲,從 0 開始與原始值 無關
  2. 有原始值的枚舉 也是佔用 1個字節
  3. 有原始值的枚舉 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

同一致

這個結果

閣下是否豁然開朗 ?

謬讚一次又何妨

第 9 個字節

一直在說 後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 類型
  • 非關聯值

    • 內存 佔用 1個字節
    • 內存中 如下標數 爲值,依次累加

粗鄙之言 ,還望體諒

如如有誤 ,還請指出

~

相關文章
相關標籤/搜索