本文主要介紹為什么結(jié)構(gòu)體是值類型,類是引用類型
值類型
前提:需要了解內(nèi)存五大區(qū),內(nèi)存五大區(qū)可以參考這篇文章iOS-底層原理 24:內(nèi)存五大區(qū),如下所示

棧區(qū)的地址 比 堆區(qū)的地址 大
棧是從
高地址->低地址,向下延伸,由系統(tǒng)自動管理,是一片連續(xù)的內(nèi)存空間堆是從
低地址->高地址,向上延伸,由程序員管理,堆空間結(jié)構(gòu)類似于鏈表,是不連續(xù)的日常開發(fā)中的溢出是指
堆棧溢出,可以理解為棧區(qū)與堆區(qū)邊界碰撞的情況全局區(qū)、常量區(qū)都存儲在Mach-O中的__TEXT cString段
我們通過一個例子來引入什么是值類型
func test(){
//棧區(qū)聲明一個地址,用來存儲age變量
var age = 18
//傳遞的值
var age2 = age
//age、age2是修改獨立內(nèi)存中的值
age = 30
age2 = 45
print("age=\(age),age2=\(age2)")
}
test()
從例子中可以得出,age存儲在棧區(qū)
- 查看
age的內(nèi)存情況,從圖中可以看出,棧區(qū)直接存儲的是值- 獲取age的棧區(qū)地址:
po withUnsafePointer(to: &age){print($0)} - 查看age內(nèi)存情況:
x/8g 0x00007ffeefbff3e0
- 獲取age的棧區(qū)地址:

- 查看
age2的情況,從下圖中可以看出,age2的賦值相當于將age中的值拿出來,賦值給了age2。其中age與age2的地址 相差了8字節(jié),從這里可以說明??臻g是連續(xù)的、且是從高到低的

所以,從上面可以說明,age就是值類型
值類型 特點
1、地址中存儲的是
值2、值類型的傳遞過程中,相當于
傳遞了一個副本,也就是所謂的深拷貝3、值傳遞過程中,并不共享狀態(tài)
結(jié)構(gòu)體
結(jié)構(gòu)體的常用寫法
//***** 寫法一 *****
struct CJLTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
var t = CJLTeacher()
//***** 寫法二 *****
struct CJLTeacher {
var age: Int
func teach(){
print("teach")
}
}
var t = CJLTeacher(age: 18)
-
在結(jié)構(gòu)體中,如果不給屬性默認值,編譯是不會報錯的。即在結(jié)構(gòu)體中屬性可以賦值,也可以不賦值
值類型-4 init方法可以重寫,也可以使用系統(tǒng)默認的
結(jié)構(gòu)體的SIL分析
-
如果
沒有init,系統(tǒng)會提供不同的默認初始化方法
值類型-5 -
如果
提供了自定義的init,就只有自定義的
值類型-6
為什么結(jié)構(gòu)體是值類型?
定義一個結(jié)構(gòu)體,并進行分析
struct CJLTeacher {
var age: Int = 18
var age2: Int = 20
}
var t = CJLTeacher()
print("end")
-
打印t:
po t,從下圖中可以發(fā)現(xiàn),t的打印直接就是值,沒有任何與地址有關(guān)的信息
值類型-7 -
獲取t的內(nèi)存地址,并查看其內(nèi)存情況
獲取地址:
po withUnsafePointer(to: &t){print($0)}查看內(nèi)存情況:
x/8g 0x0000000100008158

問題:此時將t賦值給t1,如果修改了t1,t會發(fā)生改變嗎?
- 直接打印t及t1,可以發(fā)現(xiàn)t并沒有因為t1的改變而改變,主要是因為因為
t1和t之間是值傳遞,即t1和t是不同內(nèi)存空間,是直接將t中的值拷貝至t1中。t1修改的內(nèi)存空間,是不會影響t的內(nèi)存空間的
值類型-9
SIL驗證
同樣的,我們也可以通過分析SIL來驗證結(jié)構(gòu)體是值類型
- 在
SIL文件中,我們查看結(jié)構(gòu)體的初始化方法,可以發(fā)現(xiàn)只有init,而沒有malloc,在其中看不到任何關(guān)于堆區(qū)的分配
值類型-10
總結(jié)
結(jié)構(gòu)體是值類型,且結(jié)構(gòu)體的地址就是第一個成員的內(nèi)存地址-
值類型
在內(nèi)存中直接
存儲值值類型的賦值,是一個
值傳遞的過程,即相當于拷貝了一個副本,存入不同的內(nèi)存空間,兩個空間彼此間并不共享狀態(tài)值傳遞其實就是深拷貝
引用類型
類
**類的常用寫法 **
//****** 寫法一 *******
class CJLTeacher {
var age: Int = 18
func teach(){
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t = CJLTeacher.init(20)
//****** 寫法二 *******
class CJLTeacher {
var age: Int?
func teach(){
print("teach")
}
init(_ age: Int) {
self.age = age
}
}
var t = CJLTeacher.init(20)
-
在類中,如果屬性沒有賦值,也不是可選項,編譯會報錯
引用類型-1 需要自己實現(xiàn)
init方法
為什么類是引用類型?
定義一個類,通過一個例子來說明
class CJLTeacher1 {
var age: Int = 18
var age2: Int = 20
}
var t1 = CJLTeacher1()
類初始化的對象t1,存儲在全局區(qū)
- 打印t1、t:
po t1,從圖中可以看出,t1內(nèi)存空間中存放的是地址,t中存儲的是值
引用類型-2 - 獲取t1變量的地址,并查看其內(nèi)存情況
- 獲取
t1指針地址:po withUnsafePointer(to: &t1){print($0)} - 查看t1全局區(qū)地址內(nèi)存情況:
x/8g 0x0000000100008218 - 查看t1地址中存儲的堆區(qū)地址內(nèi)存情況:
x/8g 0x00000001040088f0
- 獲取

引用類型 特點
1、地址中存儲的是
堆區(qū)地址2、
堆區(qū)地址中存儲的是值
問題1:此時將t1賦值給t2,如果修改了t2,會導(dǎo)致t1修改嗎?
- 通過
lldb調(diào)試得知,修改了t2,會導(dǎo)致t1改變,主要是因為t2、t1地址中都存儲的是同一個堆區(qū)地址,如果修改,修改是同一個堆區(qū)地址,所以修改t2會導(dǎo)致t1一起修改,即淺拷貝
引用類型-5
問題2:如果結(jié)構(gòu)體中包含類對象,此時如果修改t1中的實例對象屬性,t會改變嗎?
代碼如下所示
class CJLTeacher1 {
var age: Int = 18
var age2: Int = 20
}
struct CJLTeacher {
var age: Int = 18
var age2: Int = 20
var teacher: CJLTeacher1 = CJLTeacher1()
}
var t = CJLTeacher()
var t1 = t
t1.teacher.age = 30
//分別打印t1和t中teacher.age,結(jié)果如下
t1.teacher.age = 30
t.teacher.age = 30
從打印結(jié)果中可以看出,如果修改t1中的實例對象屬性,會導(dǎo)致t中實例對象屬性的改變。雖然在結(jié)構(gòu)體中是值傳遞,但是對于teacher,由于是引用類型,所以傳遞的依然是地址
同樣可以通過lldb調(diào)試驗證
- 打印t的地址:
po withUnsafePointer(to: &t){print($0)} - 打印t的內(nèi)存情況:
x/8g 0x0000000100008238 - 打印t中teacher地址的內(nèi)存情況:
x/8g 0x000000010070e4a0

注意:在編寫代碼過程中,應(yīng)該盡量避免值類型包含引用類型
查看當前的SIL文件,盡管CJLTeacher1是放在值類型中的,在傳遞的過程中,不管是傳遞還是賦值,teacher都是按照引用計數(shù)進行管理的

可以通過打印
teacher的引用計數(shù)來驗證我們的說法,其中teacher的引用計數(shù)為3
主要是是因為:
main中retain一次teacher.getter方法中retain一次-
teacher.setter方法中retain一次
引用類型-9
mutating
通過結(jié)構(gòu)體定義一個棧,主要有push、pop方法,此時我們需要動態(tài)修改棧中的數(shù)組
-
如果是以下這種寫法,會直接報錯,原因是
值類型本身是不允許修改屬性的
引用類型-10 將push方法改成下面的方式,查看
SIL文件中的push函數(shù)
struct CJLStack {
var items: [Int] = []
func push(_ item: Int){
print(item)
}
}

從圖中可以看出,
push函數(shù)除了item,還有一個默認參數(shù)self,self是let類型,表示不允許修改
- 嘗試1:如果將push函數(shù)修改成下面這樣,可以添加進去嗎?
struct CJLStack {
var items: [Int] = []
func push(_ item: Int){
var s = self
s.items.append(item)
}
}
打印結(jié)果如下

可以得出上面的代碼并不能將item添加進去,因為
s是另一個結(jié)構(gòu)體對象,相當于值拷貝,此時調(diào)用push是將item添加到s的數(shù)組中了
- 根據(jù)前文中的錯誤提示,給push添加
mutating,發(fā)現(xiàn)可以添加到數(shù)組了
struct CJLStack {
var items: [Int] = []
mutating func push(_ item: Int){
items.append(item)
}
}
查看其SIL文件,找到push函數(shù),發(fā)現(xiàn)與之前有所不同,push添加mutating(只用于值類型)后,本質(zhì)上是給值類型函數(shù)添加了inout關(guān)鍵字,相當于在值傳遞的過程中,傳遞的是引用(即地址)

inout關(guān)鍵字
一般情況下,在函數(shù)的聲明中,默認的參數(shù)都是不可變的,如果想要直接修改,需要給參數(shù)加上inout關(guān)鍵字
- 未加
inout關(guān)鍵字,給參數(shù)賦值,編譯報錯
引用類型-14 - 添加
inout關(guān)鍵字,可以給參數(shù)賦值
引用類型-15
總結(jié)
1、結(jié)構(gòu)體中的函數(shù)如果想修改其中的屬性,需要在函數(shù)前加上
mutating,而類則不用2、
mutating本質(zhì)也是加一個inout修飾的self3、
Inout相當于取地址,可以理解為地址傳遞,即引用4、
mutating修飾方法,而inout修飾參數(shù)
總結(jié)
通過上述LLDB查看結(jié)構(gòu)體 & 類的內(nèi)存模型,有以下總結(jié):
值類型,相當于一個本地excel,當我們通過QQ傳給你一個excel時,就相當于一個值類型,你修改了什么我們這邊是不知道的引用類型,相當于一個在線表格,當我們和你共同編輯一個在先表格時,就相當于一個引用類型,兩邊都會看到修改的內(nèi)容結(jié)構(gòu)體中函數(shù)修改屬性, 需要在函數(shù)前添加mutating關(guān)鍵字,本質(zhì)是給函數(shù)的默認參數(shù)self添加了inout關(guān)鍵字,將self從let常量改成了var變量
方法調(diào)度
通過上面的分析,我們有以下疑問:結(jié)構(gòu)體和類的方法存儲在哪里?下面來一一進行分析
靜態(tài)派發(fā)
值類型對象的函數(shù)的調(diào)用方式是靜態(tài)調(diào)用,即直接地址調(diào)用,調(diào)用函數(shù)指針,這個函數(shù)指針在編譯、鏈接完成后就已經(jīng)確定了,存放在代碼段,而結(jié)構(gòu)體內(nèi)部并不存放方法。因此可以直接通過地址直接調(diào)用
-
結(jié)構(gòu)體函數(shù)調(diào)試如下所示
image -
打開打開demo的
Mach-O可執(zhí)行文件,其中的__text段,就是所謂的代碼段,需要執(zhí)行的匯編指令都在這里
image
對于上面的分析,還有個疑問:直接地址調(diào)用后面是符號,這個符號哪里來的?

是從
Mach-O文件中的符號表Symbol Tables,但是符號表中并不存儲字符串,字符串存儲在String Table(字符串表,存放了所有的變量名和函數(shù)名,以字符串形式存儲),然后根據(jù)符號表中的偏移值到字符串中查找對應(yīng)的字符,然后進行命名重整:工程名+類名+函數(shù)名,如下所示
-
Symbol Table:存儲符號位于字符串表的位置
-
Dynamic Symbol Table:動態(tài)庫函數(shù)位于符號表的偏移信息
還可以通過終端命令nm,獲取項目中的符號表
查看符號表:
nm mach-o文件路徑-
通過命令還原符號名稱:
xcrun swift-demangle 符號
image 如果將
edit scheme -> run中的debug改成release,編譯后查看,在可執(zhí)行文件目錄下,多一個后綴為dSYM的文件,此時,再去Mach-O文件中查找teach,發(fā)現(xiàn)是找不到,其主要原因是因為靜態(tài)鏈接的函數(shù),實際上是不需要符號的,一旦編譯完成,其地址確定后,當前的符號表就會刪除當前函數(shù)對應(yīng)的符號,在release環(huán)境下,符號表中存儲的只是不能確定地址的符號-
對于不能確定地址的符號,是在
運行時確定的,即函數(shù)第一次調(diào)用時(相當于懶加載),例如print,是通過dyld_stub_bind確定地址的(這個在最新版的12.2中通過斷點調(diào)試并未找到,后續(xù)待繼續(xù)驗證,有不同見解的,歡迎留言指出)
image
函數(shù)符號命名規(guī)則
- 對于
C函數(shù)來說,命名的重整規(guī)則就是在函數(shù)名之前加_(注意:C中不允許函數(shù)重載,因為沒有辦法區(qū)分)
#include <stdio.h>
void test(){ }

-
對于OC來說,也不支持函數(shù)重載,其符號命名規(guī)則是
-[類名 函數(shù)名]
image 對于Swift來說,是云溪函數(shù)重載,主要是因為swift中的重整命名規(guī)則比較復(fù)雜,可以確保函數(shù)符號的唯一性
補充:ASLR
關(guān)于ASLR的詳細說明參考iOS-底層原理 32:啟動優(yōu)化(一)基本概念中對于ASLR的解釋,下面是針對函數(shù)地址的一個驗證
-
通過運行發(fā)現(xiàn),Mach-O中的地址與調(diào)試時直接獲取的地址是由一定偏差的,其主要原因是實際調(diào)用時地址多了一個
ASLR(地址空間布局隨機化 address space layout randomizes)
image -
可以通過
image list查看,其中0x0000000100000000是程序運行的首地址,后8位是隨機偏移00000000(即ASLR)
image 將Mach-O中的文件地址0x0000000100003D50 + 0x00000000 = 0x100003D50,正好對應(yīng)上面調(diào)用的地址
動態(tài)派發(fā)
匯編指令補充
-
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:將寄存器中的值寫入到內(nèi)存中- str x0, [x0, x8] 將寄存器x0的值保存到內(nèi)存[x0 + x8]處
-
bl:跳轉(zhuǎn)到某地址
探索class的調(diào)度方式
首先介紹下V_Table在SIL文件中的格式
//聲明sil vtable關(guān)鍵字
decl ::= sil-vtable
//sil vtable中包含 關(guān)鍵字、標識(即類名)、所有的方法
2 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
//方法中包含了聲明以及函數(shù)名稱
3 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na
me
例如,以CJLTacher為例,其SIL中的v-table如下所示
class CJLTeacher{
func teach(){}
func teach2(){}
func teach3(){}
func teach4(){}
@objc deinit{}
init(){}
}

sil_vtable:關(guān)鍵字CJLTeacher:表示是CJLTeacher類的函數(shù)表其次就是當前方法的聲明對應(yīng)著方法的名稱
-
函數(shù)表 可以理解為
數(shù)組,聲明在 class內(nèi)部的方法在不加任何關(guān)鍵字修飾的過程中,是連續(xù)存放在我們當前的地址空間中的。這一點,可以通過斷點來印證,
image-
register read x0,此時的地址和 實例對象的地址是相同的,其中x8實例對象地址,即首地址
image
-
觀察這幾個方法的偏移地址,可以發(fā)現(xiàn)方法是連續(xù)存放的,正好對應(yīng)V-Table函數(shù)表中的排放順序,即是按照定義順序排放在函數(shù)表中

函數(shù)表源碼探索
下面來進行函數(shù)表底層的源碼探索
- 源碼中搜索
initClassVTable,并加上斷點,然后寫上源碼進行調(diào)試
image
其內(nèi)部是通過for循環(huán)編碼,然后offset+index偏移,然后獲取method,將其存入到偏移后的內(nèi)存中,從這里可以印證函數(shù)是連續(xù)存放的
對于class中函數(shù)來說,類的方法調(diào)度是通過V-Taable,其本質(zhì)就是一個連續(xù)的內(nèi)存空間(數(shù)組結(jié)構(gòu))。
問題:如果更改方法聲明的位置呢?例如extension中的函數(shù),此時的函數(shù)調(diào)度方式還是函數(shù)表調(diào)度嗎?
通過以下代碼驗證
- 定義一個CJLTeacher的extension
extension CJLTeacher{
func teach5(){ print("teach5") }
}
- 在定義一個子類
CJLStudent繼承自CJLTeacher,查看SIL中的V-Table
class CJLStudent: CJLTeacher{}
- 查看SIL文件,發(fā)現(xiàn)子類只繼承了class中定義的函數(shù),即函數(shù)表中的函數(shù)
image
其原因是因為子類將父類的函數(shù)表全部繼承了,如果此時子類增加函數(shù),會繼續(xù)在連續(xù)的地址中插入,假設(shè)extension函數(shù)也是在函數(shù)表中,則意味著子類也有,但是子類無法并沒有相關(guān)的指針記錄函數(shù) 是父類方法 還是 子類方法,所以不知道方法該從哪里插入,導(dǎo)致extension中的函數(shù)無法安全的放入子類中。所以在這里可以側(cè)面證明extension中的方法是直接調(diào)用的,且只屬于類,子類是無法繼承的
開發(fā)注意點:
- 繼承方法和屬性,不能寫extension中。
- 而extension中創(chuàng)建的函數(shù),一定是只屬于自己類,但是其
子類也有其訪問權(quán)限,只是不能繼承和重寫,如下所示
extension CJLTeacher{
var age: Int{
get{
return 18
}
}
func teach(){
print("teach")
}
}
class CJLMiddleTeacher: CJLTeacher{
override func study() {
print("CJLMiddleTeacher study")
}
}
var t = CJLMiddleTeacher()
//子類有父類extension中方法的訪問權(quán)限,只是不能繼承和重寫
t.teach()
t.study()
print(t.age)
<!--運行結(jié)果-->
teach
CJLMiddleTeacher study
18
final、@objc、dynamic修飾函數(shù)
final 修飾
-
final修飾的方法是直接調(diào)度的,可以通過SIL驗證 + 斷點驗證
class CJLTeacher {
final func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
init(){}
}

@objc 修飾
使用@objc關(guān)鍵字是將swift中的方法暴露給OC
class CJLTeacher{
@objc func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
init(){}
}
通過SIL+斷點調(diào)試,發(fā)現(xiàn)@objc修飾的方法是 函數(shù)表調(diào)度

【小技巧】:混編頭文件查看方式:查看項目名-Swift.h頭文件

- 如果只是通過@objc修飾函數(shù),OC還是無法調(diào)用swift方法的,因此如果想要
OC訪問swift,class需要繼承NSObject
<!--swift類-->
class CJLTeacher: NSObject {
@objc func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
override init(){}
}
<!--橋接文件中的聲明-->
SWIFT_CLASS("_TtC9_3_指針10CJLTeacher")
@interface CJLTeacher : NSObject
- (void)teach;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
<!--OC調(diào)用-->
//1、導(dǎo)入swift頭文件
#import "CJLOCTest-Swift.h"
//2、調(diào)用
CJLTeacher *t = [[CJLTeacher alloc] init];
[t teach];
查看SIL文件發(fā)現(xiàn)被@objc修飾的函數(shù)聲明有兩個:swift + OC(內(nèi)部調(diào)用的swift中的teach函數(shù))

即在SIL文件中生成了兩個方法
- swift原有的函數(shù)
- @objc標記暴露給OC來使用的函數(shù): 內(nèi)部調(diào)用swift的
dynamic 修飾
以下面代碼為例,查看dynamic修飾的函數(shù)的調(diào)度方式
class CJLTeacher: NSObject {
dynamic func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
override init(){}
}
其中teach函數(shù)的調(diào)度還是 函數(shù)表調(diào)度,可以通過斷點調(diào)試驗證,使用dynamic的意思是可以動態(tài)修改,意味著當類繼承自NSObject時,可以使用method-swizzling
@objc + dynamic
class CJLTeacher{
@objc dynamic func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
init(){}
}
通過斷點調(diào)試,走的是objc_msgSend流程,即 動態(tài)消息轉(zhuǎn)發(fā)

場景:swift中實現(xiàn)方法交換
在swift中的需要交換的函數(shù)前,使用dynamic修飾,然后通過:@_dynamicReplacement(for: 函數(shù)符號)進行交換,如下所示
class CJLTeacher: NSObject {
dynamic func teach(){ print("teach") }
func teach2(){ print("teach2") }
func teach3(){ print("teach3") }
func teach4(){ print("teach4") }
@objc deinit{}
override init(){}
}
extension CJLTeacher{
@_dynamicReplacement(for: teach)
func teach5(){
print("teach5")
}
}
將teach方法替換成了teach5

- 如果teach沒有實現(xiàn) / 如果去掉
dynamic修飾符,會報錯
image
總結(jié)
struct是值類型,其中函數(shù)的調(diào)度屬于直接調(diào)用地址,即靜態(tài)調(diào)度class是引用類型,其中函數(shù)的調(diào)度是通過V-Table函數(shù)表來進行調(diào)度的,即動態(tài)調(diào)度extension中的函數(shù)調(diào)度方式是直接調(diào)度final修飾的函數(shù)調(diào)度方式是直接調(diào)度@objc修飾的函數(shù)調(diào)度方式是函數(shù)表調(diào)度,如果OC中需要使用,class還必須繼承NSObjectdynamic修飾的函數(shù)的調(diào)度方式是函數(shù)表調(diào)度,使函數(shù)具有動態(tài)性@objc + dynamic組合修飾的函數(shù)調(diào)度,是執(zhí)行的是objc_msgSend流程,即動態(tài)消息轉(zhuǎn)發(fā)
補充:內(nèi)存插件
主要補充內(nèi)存插件libfooplugin.dylib安裝及使用
安裝 & 使用
在跟目下創(chuàng)建.lldbinit文件
vim /.lldbinit然后輸入
plugin load libfooplugin.dylib路徑使用:在lldb 調(diào)試中輸入 --
cat address 地址
可以在這里下載插件文件,密碼: go4q
內(nèi)存分區(qū)實踐
堆區(qū)
有以下代碼,通過cat查看t屬于哪個區(qū)
class CJLTeacher{
func teach(){
}
}
let t = CJLTeacher()

從結(jié)果中可以看出,是在堆區(qū),即
heap pointer
棧區(qū)
查看以下代碼的內(nèi)存地址位于哪個區(qū)?
func test(){
var age: Int = 10
print(age)
}

從結(jié)果來看,位于
棧區(qū),即stack pointer
全局區(qū)
對于C的分析
下面是C語言的部分代碼,查看其變量的內(nèi)存地址
//全局已初始化變量
int a = 10;
//全局未初始化變量
int age;
//全局靜態(tài)變量
static int age2 = 30;
int main(int argc, const char * argv[]) {
char *p = "CJLTeacher";
printf("%d", a);
printf("%d", age2);
return 0;
}
-
查看a(全局已初始化變量)的內(nèi)存地址
image
其中__DATA.__data表示segment.section,這里的位置和全局區(qū)并不沖突,因為一個是人為的內(nèi)存分配(內(nèi)存布局分區(qū)),一個是Mach-O的segment.section段中,是文件的格式劃分
image -
查看age(全局未初始化變量)的內(nèi)存地址
image
age在Mach-O文件中,放在了__DATA.__common段,主要放的就是未初始化的符號聲明(mach-o相比內(nèi)存劃分更細,主要是為了更好的定位符號),當然此時的age在內(nèi)存中依然在全局區(qū) -
查看
age2(全局已初始化靜態(tài)變量)的內(nèi)存地址(其中需要注意:age2必須使用才能找到,否則會報錯)
image -
觀察3個變量的地址,其地址都是相鄰的,因為在內(nèi)存中都放在了全局區(qū),觀察其內(nèi)存地址,可以發(fā)現(xiàn),在
全局區(qū)中,未初始化變量地址 比 已初始化變量地址 高
image -
如果定義了一個
char *p = "CJLTeacher",查看*p,存儲在__TEXT.cstring段,內(nèi)存中存儲在常量區(qū)
image -
如果是
const修飾的變量呢?存放在Mach-O文件中的__TEXT.__const段
image 如果使用
static + const修飾變量,此時變量在哪?**
static const int age3 = 40;
- 查看
age3的內(nèi)存地址,地址特別大,而且使用cat查看不了,因為mach-o沒有記錄,age3 就是30,即使用static+const修飾的變量就相當于直接替換
image
對于swift的分析
let age = 10
由于是不可變所以不能通過po+cat查看內(nèi)存,通過匯編 首地址+偏移 來獲取age的內(nèi)存,發(fā)現(xiàn)是在Mach-O的__DATA.__common段

從這里可以發(fā)現(xiàn),這與C中是有所區(qū)別的。swift的不同之處:已經(jīng)初始化的全局變量放在
__DATA.__common段,猜測是因為 age開始是被標記為未初始化的,當我們執(zhí)行代碼之后才將10存儲到對應(yīng)的內(nèi)存地址中
- 如果是
var修飾的變量呢?可以發(fā)現(xiàn)與let是一致的,還是__DATA.__common段
var age2 = 10

總結(jié)
- 對于C語言中全局變量,根據(jù)是否已經(jīng)初始化,存儲在Mach-O中存儲位置是不同的
已初始化的全局變量:__DATA.__data未初始化的全局變量:__DATA.__common已初始化的全局靜態(tài)變量,即static修飾:__DATA.__data對于
char *p類型的字符:__TEXT.cstringconst修飾的全局變量:__TEXT.__conststatic+const修飾的全局變量:Mach-O中沒有記錄
































