值類(lèi)型
我們先大概了解下內(nèi)存的五大區(qū)
- 棧的地址比堆的地址大
- 棧區(qū)內(nèi)存由
系統(tǒng)管理
的連續(xù)空間,地址從高地址->低地址
- 堆區(qū)內(nèi)存由
程序員
管理,地址從低地址->高地址
- 堆區(qū)分配不連續(xù),類(lèi)似鏈表
- 日常開(kāi)發(fā)中的溢出是指
堆棧溢出
,可以理解為棧區(qū)與堆區(qū)邊界碰撞的情況 -
全局區(qū)、常量區(qū)
都存儲(chǔ)在Mach-O
中的__TEXT cString
段
我們首先看一個(gè)例子
func test(){
var age = 18
var age2 = age
age = 30
age2 = 45
print("age=\(age),age2=\(age2)")
}
test()
從例子中可以得出,age存儲(chǔ)在棧區(qū)
- 輸出age地址
- 獲取age的棧區(qū)地址:
po withUnsafePointer(to: &age){print($0)}
(指針輸出,后面會(huì)講) - 查看age內(nèi)存情況:
x/8g 0x00007ffeefbff3e0
x/8g格式化輸出,就是存儲(chǔ)的18的值
age賦值給age2后再次輸出,發(fā)現(xiàn)發(fā)地址是連續(xù)的,且從高到低
值類(lèi)型特點(diǎn):
- 地址存儲(chǔ)的就是
值
- 傳遞的是值的
副本
,也就是深拷貝
- 傳遞過(guò)程中不共享狀態(tài)
結(jié)構(gòu)體
結(jié)構(gòu)體就是結(jié)構(gòu)體
struct HZMPerson{
var age: Int = 18
//結(jié)構(gòu)體可以不用默認(rèn)值
var age2: Int
//避免值類(lèi)型里面包含引用類(lèi)型
// var test : HZMPerson2 = HZMPerson2()
}
//值類(lèi)型:值
var Q = HZMPerson()
//結(jié)構(gòu)體傳遞的過(guò)程不共享狀態(tài)
var Q2 = Q
Q2.age = 30
打印Q發(fā)現(xiàn),直接就是值,沒(méi)有任何與地址有關(guān)的信息
- 獲取地址:
po withUnsafePointer(to: &Q){print($0)}
- 查看內(nèi)存情況:
x/8g 0x0000000100008178
總結(jié):
-
結(jié)構(gòu)體是值類(lèi)型
,且結(jié)構(gòu)體的地址就是第一個(gè)成員的內(nèi)存地址 - 在結(jié)構(gòu)體中,如果不給屬性默認(rèn)值,編譯是不會(huì)報(bào)錯(cuò)的。即在結(jié)構(gòu)體中屬性可以賦值,也可以不賦值
值類(lèi)型: - 在內(nèi)存中直接
存儲(chǔ)值
- 值類(lèi)型的賦值,是一個(gè)
值傳遞
的過(guò)程,即相當(dāng)于拷貝了一個(gè)副本,存入不同的內(nèi)存空間,兩個(gè)空間彼此間并不共享狀態(tài)
- 值傳遞其實(shí)就是
深拷貝
引用類(lèi)型
引用類(lèi)型:地址,相當(dāng)于在線(xiàn)表格
類(lèi)
小tips:在類(lèi)中,如果屬性沒(méi)有賦值,也不是可選項(xiàng),編譯會(huì)報(bào)錯(cuò)
需要自己實(shí)現(xiàn)init
方法
- 打印H,從圖中可以看出,H內(nèi)存空間中存放的是地址
- 通過(guò)lldb調(diào)試得知,修改了H2,會(huì)導(dǎo)致H改變,主要是因?yàn)镠2、H1地址中都存儲(chǔ)的是 同一個(gè)堆區(qū)地址,如果修改,修改是同一個(gè)堆區(qū)地址,所以修改H2會(huì)導(dǎo)致H1一起修改,即淺拷貝
注意:
1、地址中存儲(chǔ)的是堆區(qū)地址
2、堆區(qū)地址中存儲(chǔ)的是值
3、在編寫(xiě)代碼過(guò)程中,應(yīng)該盡量避免值類(lèi)型包含引用類(lèi)型
mutating&inout
mutating
通過(guò)結(jié)構(gòu)體
定義一個(gè)棧
,主要有push、pop方法,此時(shí)我們需要?jiǎng)討B(tài)修改棧中的數(shù)組
如果是以下這種寫(xiě)法,會(huì)直接報(bào)錯(cuò),原因是值類(lèi)型本身是不允許修改屬性
的
我們?cè)俅瓮ㄟ^(guò)
SIL
文件查看,發(fā)現(xiàn)self
是let
類(lèi)型,當(dāng)我們修改items
時(shí)就相當(dāng)于修改self
,所以不可修改
。
當(dāng)我們嘗試使用另一種方式來(lái)修改,實(shí)際最終打印的還是空
當(dāng)我們?yōu)楹瘮?shù)添加一個(gè)mutating修飾的時(shí)候,發(fā)現(xiàn)可以進(jìn)行修改了,這是為什么?我們來(lái)查看下SIL文件
查看其
SIL
文件,找到push
函數(shù),發(fā)現(xiàn)與之前有所不同,push
添加mutaing
(只用于值類(lèi)型)后,本質(zhì)上是給值類(lèi)型函數(shù)添加了inout
關(guān)鍵字,相當(dāng)于在值傳遞的過(guò)程中,傳遞
的是引用
(即地址)
inout
一般情況下,在函數(shù)的聲明中,默認(rèn)的參數(shù)都是不可變的
如果想要直接修改,需要給參數(shù)加上
inout
關(guān)鍵字
總結(jié):
1、結(jié)構(gòu)體中的函數(shù)如果想修改其中的屬性,需要在函數(shù)前加上mutating
,而類(lèi)則不用
2、mutating
本質(zhì)也是加一個(gè) inout
修飾的self
3、Inout
相當(dāng)于取地址
,可以理解為地址傳遞
,即引用
4、mutating
修飾方法,而inout
修飾參數(shù)
總結(jié)
通過(guò)上述LLDB查看結(jié)構(gòu)體 & 類(lèi)的內(nèi)存模型,有以下總結(jié):
值類(lèi)型
,相當(dāng)于一個(gè)本地excel
,當(dāng)我們通過(guò)QQ傳給你一個(gè)excel時(shí),就相當(dāng)于一個(gè)值類(lèi)型,你修改了什么我們這邊是不知道的引用類(lèi)型
,相當(dāng)于一個(gè)在線(xiàn)表格,當(dāng)我們和你共同編輯一個(gè)在線(xiàn)表格
時(shí),就相當(dāng)于一個(gè)引用類(lèi)型,兩邊都會(huì)看到修改的內(nèi)容結(jié)構(gòu)體
中函數(shù)修改屬性
, 需要在函數(shù)前添加mutating
關(guān)鍵字,本質(zhì)是給函數(shù)的默認(rèn)參數(shù)self
添加了inout
關(guān)鍵字,將self
從let
常量改成了var
變量
方法調(diào)度
靜態(tài)派發(fā)
callq
就是一個(gè)指令的跳轉(zhuǎn)
,就是執(zhí)行我們的函數(shù)方法。值類(lèi)型對(duì)象的函數(shù)的調(diào)用方式是靜態(tài)調(diào)用
,即直接地址調(diào)用
,調(diào)用函數(shù)指針,這個(gè)函數(shù)指針在編譯、鏈接完成后就已經(jīng)確定了
,存放在代碼段,而結(jié)構(gòu)體內(nèi)部并不存放方法。因此可以直接通過(guò)地址直接調(diào)用
打開(kāi)打開(kāi)demo的
Mach-O
可執(zhí)行文件,其中的__text
段,就是所謂的代碼段,需要執(zhí)行的匯編指令都在這里
直接地址調(diào)用后面是符號(hào),這個(gè)符號(hào)哪里來(lái)的?
是從Mach-O
文件中的符號(hào)表Symbol Tables
,但是符號(hào)表中并不存儲(chǔ)字符串
,字符串存儲(chǔ)在String Table
(字符串表,存放了所有的變量名和函數(shù)名,以字符串形式存儲(chǔ)),然后根據(jù)符號(hào)表中的偏移值到字符串中查找對(duì)應(yīng)的字符,然后進(jìn)行命名重整:工程名+類(lèi)名+函數(shù)名
,如下所示
- Symbol Table:存儲(chǔ)符號(hào)位于字符串表的位置
- Dynamic Symbol Table:動(dòng)態(tài)庫(kù)函數(shù)位于符號(hào)表的偏移信息
命名重整規(guī)則先不用考慮,因?yàn)橛忻羁梢灾苯舆€原
查看符號(hào)表:nm mach-o文件路徑
通過(guò)命令還原符號(hào)名稱(chēng):
xcrun swift-demangle
符號(hào)
如果將
edit scheme -> run
中的debug
改成release
,編譯后查看,在可執(zhí)行文件目錄下,多一個(gè)后綴為dSYM
的文件,此時(shí),再去Mach-O
文件中查找teach
,發(fā)現(xiàn)是找不到,其主要原因是因?yàn)?code>靜態(tài)鏈接的函數(shù),實(shí)際上是不需要符號(hào)的,一旦編譯完成,其地址確定后,當(dāng)前的符號(hào)表就會(huì)刪除當(dāng)前函數(shù)對(duì)應(yīng)的符號(hào),在release環(huán)境下,符號(hào)表
中存儲(chǔ)的只是不能確定地址的符號(hào)
函數(shù)符號(hào)命名規(guī)則
#include <stdio.h>
void test(){ }
對(duì)于C函數(shù)來(lái)說(shuō),命名的重整規(guī)則就是在函數(shù)名之前加_(注意:C中不允許函數(shù)重載,因?yàn)闆](méi)有辦法區(qū)分)
對(duì)于OC來(lái)說(shuō),也不支持函數(shù)重載,其符號(hào)命名規(guī)則是-[類(lèi)名 函數(shù)名]
Swift通過(guò)復(fù)雜的命名重整規(guī)則,確保符號(hào)的唯一性,這樣這兩個(gè)方法才不會(huì)報(bào)重命名的錯(cuò)誤,OC與C都不行 C++可以
補(bǔ)充:ASLR
通過(guò)運(yùn)行發(fā)現(xiàn),
Mach-O
中的地址與調(diào)試時(shí)直接獲取的地址是有一定偏差
的,其主要原因是實(shí)際調(diào)用時(shí)地址多了一個(gè)ASLR
(地址空間布局隨機(jī)化 address space layout randomizes可以通過(guò)
image list
查看,其中0x0000000100000000是程序運(yùn)行的首地址,后8位是隨機(jī)偏移00000000(即ASLR)匯編地址 =
Mach-O
中的地址(靜態(tài)基地址) +image list
中首地址的后8位(ASLR)
動(dòng)態(tài)派發(fā)
匯編指令補(bǔ)充
-
blr
:帶返回的跳轉(zhuǎn)指令,跳轉(zhuǎn)到指令后邊跟隨寄存器中保存的地址 -
mov
:將某一寄存器的值復(fù)制到另一寄存器(只能用于寄存器與起存起或者 寄存器與常量之間 傳值,不能用于內(nèi)存地址) -
mov
x1, x0 將寄存器x0的值復(fù)制到寄存器x1中 -
ldr
:將內(nèi)存中的值讀取到寄存器中
ldr x0, [x1, x2] 將寄存器x1和寄存器x2 相加作為地址,取該內(nèi)存地址的值翻入寄存器x0中 -
str
:將寄存器中的值寫(xiě)入到內(nèi)存中
str x0, [x0, x8] 將寄存器x0的值保存到內(nèi)存[x0 + x8]處 -
bl
:跳轉(zhuǎn)到某地址