匯編分析結(jié)構(gòu)體嚎京、類的內(nèi)存布局

結(jié)構(gòu)體

先看一下簡單的語法書寫
栗子1

struct Point {
    var x: Int = 0
    var y: Int
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(y:20)//錯誤
var p3 = Point(x: 10)//錯誤
var p4 = Point()//錯誤

栗子2

struct Point {
    var x: Int
    var y: Int
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(x: 10)//錯誤
var p3 = Point( y:20)//錯誤
var p4 = Point()//錯誤
struct Point {
    var x: Int = 0
    var y: Int = 0
}
var p1 = Point(x: 10, y:20)// 正確
var p2 = Point(x: 10)//正確
var p3 = Point(y:20)//正確
var p4 = Point()//正確

編譯器干了啥?
編譯器會根據(jù)情況,可能會為結(jié)構(gòu)體生成生成多個初始化器,宗旨是:保證所有成員都有初始值.

??下面的代碼能編譯通過嗎?

struct Point {
    var x: Int?
    var y: Int?
}
var p1 = Point(x: 10, y:20)
var p2 = Point(x: 10)
var p3 = Point( y:20)
var p4 = Point()

自定義初始化器

struct Point {
    var x: Int = 0
    var y: Int = 0
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}
var p1 = Point(x: 10, y:20)//正確
var p2 = Point(x: 10)//錯誤
var p3 = Point( y:20)//錯誤
var p4 = Point()//錯誤

窺看初始化器本質(zhì)

栗子1

struct Point {
    var x: Int = 0
    var y: Int = 0
}
var p = Point()

栗子2

struct Point {
    var x: Int
    var y: Int
    init() {
        self.x = 0
        self.y = 0
    }
}
var p = Point()

栗子1和栗子2完全等價

結(jié)構(gòu)體的本質(zhì)

func testStruct() {
    struct Point {
        var x: Int = 10
        var y: Int = 20
        var b: Bool = false
    }
    var p = Point.init(x: 10, y: 10, b: true)
    print(Mems.memStr(ofVal: &p)) 
    print(MemoryLayout<Point>.size)
    print(MemoryLayout<Point>.stride)
    print(MemoryLayout<Point>.alignment)
}
testStruct()

打印結(jié)果:

0x000000000000000a 0x000000000000000a 0x0000000000000001
17
24
8
Program ended with exit code: 0

類的定義和結(jié)構(gòu)體是相似的,但是編譯器并沒有為類自動生成可以傳入成員值的初始化器.

class Point {
    var x: Int = 0
    var y: Int = 0
    func test() { }
}
var p1 = Point()//正確
var p2 = Point(x: 10, y: 20)//錯誤
var p3 = Point(x: 10)//錯誤
var p4 = Point(y: 20)//錯誤

對比結(jié)構(gòu)體

struct Point {
    var x: Int = 0
    var y: Int = 0
    func test() { }
}
var p1 = Point()//正確
var p2 = Point(x: 10, y: 20)//正確
var p3 = Point(x: 10)//正確
var p4 = Point(y: 20)//正確

結(jié)構(gòu)體和類的本質(zhì)區(qū)別

結(jié)構(gòu)體和枚舉都是值類型
類是引用類型(指針類型)

class Size {
    var width = 1
    var height = 2
}
struct Point {
    var x = 3
    var y = 4
}
func test() {
    var size = Size()
    var point = Point()
}

問題:函數(shù)調(diào)用時,內(nèi)存在哪里?
0x10000就是point結(jié)構(gòu)體變量的內(nèi)存地址
0x10008就是point結(jié)構(gòu)體中y的內(nèi)存地址
size是指針變量,占用8個字節(jié),放在zhan里.


內(nèi)存分布

注:上圖指針針對的是64bit環(huán)境

對象的堆空間申請過程

??怎么看有木有在堆空間呢?
在swift里面就看有木有調(diào)用alloc叮盘、malloc這些函數(shù).

func testClassAndStruct() {
    class Size {
        var w = 1
        var h = 2
    }
   struct Point {
        var x = 3
        var y = 4
    }
    var s = Size()
    var p = Point()
}

在swift中,創(chuàng)建類的實(shí)例對象,要向堆空間申請內(nèi)存,大概流程如下:

  • Class.__allocating_init()
  • libswiftCore.dyld:swift_allocObject
  • libswiftCore.dyld:swift_slowAlloc
  • libsystem_malloc.dylib:malloc
   class Point {
        var x = 3
        var y = 4
        var b = true
    }
var p = Point()
class_getInstanceSize(type(of:p))// 40
class_getInstanceSize(Point.self)// 40
func testClassAndStruct() {
    class Size {
        var w = 1
        var h = 2
    }

    struct Point {
        var x = 3
        var y = 4
    }

    print("MemoryLayout<Size>.stride",MemoryLayout<Size>.stride)
    print("MemoryLayout<Point>.stride",MemoryLayout<Point>.stride)
    print("-------------------------")

    var s = Size()
    print("s變量的內(nèi)存地址:",Mems.ptr(ofVal: &s))
    print("s變量的內(nèi)存的內(nèi)容:",Mems.memStr(ofVal: &s))
    print("-------------------------")

    print("s所指向的內(nèi)存地址:",Mems.ptr(ofRef: s))
    print("s所指向的內(nèi)存的內(nèi)容:",Mems.memStr(ofRef: s))
    print("-------------------------")

    var p = Point()
    print("p變量的內(nèi)存地址:",Mems.ptr(ofVal: &p))
    print("p變量的內(nèi)存的內(nèi)容:",Mems.memStr(ofVal: &p))
}

打印結(jié)果:

MemoryLayout<Size>.stride 8
MemoryLayout<Point>.stride 16
-------------------------
s變量的內(nèi)存地址: 0x00007ffeefbff4a0
s變量的內(nèi)存的內(nèi)容: 0x00000001006060e0
-------------------------
s所指向的內(nèi)存地址: 0x00000001006060e0
s所指向的內(nèi)存的內(nèi)容: 0x0000000100008298 0x0000000200000002 0x0000000000000001 0x0000000000000002
-------------------------
p變量的內(nèi)存地址: 0x00007ffeefbff480
p變量的內(nèi)存的內(nèi)容: 0x0000000000000003 0x0000000000000004
Program ended with exit code: 0

由此可見:
1秧廉、s變量的內(nèi)存地址0x00007ffeefbff4a0怒竿、p變量的內(nèi)存地址0x00007ffeefbff480相鄰,相差16個字節(jié);
2、0x00000001006060e0 這個就是堆空間對象的地址;
3湃望、MemoryLayout<Size>.stride為8,s所指向的內(nèi)存的內(nèi)容為32?
4、如何知道一個對象創(chuàng)建出來占用多少堆空間地址?

var ptr = malloc(16)
print(malloc_size(ptr))// 16

小常識:在Mac痰驱、iOS中的malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù)

通過class_getInstanceSize可以得知類的對象真正使用的內(nèi)存大小

來一個問題??: 結(jié)構(gòu)體內(nèi)存一定在棧里嗎?
不一定,要看你的結(jié)構(gòu)體變量在哪里定義的.

  • 如果結(jié)構(gòu)體變量是在函數(shù)里定義,它的內(nèi)存在椫ぐ牛空間.
    如:
    func test() {
        var p = Ponit()//結(jié)構(gòu)體
    }
  • 如果結(jié)構(gòu)體變量是在外邊定義的,那它的內(nèi)存就在數(shù)據(jù)段(全局區(qū))是一個全局變量.
    如:
var p = Ponit()//結(jié)構(gòu)體
class Size {
    var w = 1
    var h = 2
}
  • 如果結(jié)構(gòu)體變量在一個類里面創(chuàng)建,那它的內(nèi)存就在堆空間.
    如:
class Size {
    var w = 1
    var h = 2
    var p = Ponit()//結(jié)構(gòu)體
}

思考??:

class Size {
    var w = 1
    var h = 2
    func test() {
        var p = Ponit()//結(jié)構(gòu)體
    }
}

上面這個結(jié)構(gòu)體變量p內(nèi)存地址在哪里?

聯(lián)想一下: 類的內(nèi)存在哪里呢?
無論在哪里創(chuàng)建對象,這個對象的內(nèi)存一定在堆空間.只不過指針變量的內(nèi)存在的位置不一定.函數(shù)里面定義就在棧空間,函數(shù)外邊定義就在(數(shù)據(jù)段)全局區(qū).

值類型

值類型賦值給var萄唇、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份(深copy).

func testValueType() {
    struct Point {
        var x: Int
        var y: Int
    }
    var p1 = Point(x: 10, y: 20)
    var p2 = p1
    p2.x = 11
    p2.y = 21
    print("1111")
}

匯編觀察上面的值類型

mov qword ptr [rbp - 0x10], rax  //  rbp - 0x10  0x1000   p1的內(nèi)存地址
mov qword ptr [rbp - 0x8], rdx   //  rbp - 0x8   0x1008   

mov qword ptr [rbp - 0x20], rax  //  rbp - 0x20           p2的內(nèi)存地址
mov qword ptr [rbp - 0x18], rdx  //  rbp - 0x18

mov qword ptr [rbp - 0x20], 0xb   // 11   給了p2
mov qword ptr [rbp - 0x18], 0x15  // 22

全局定義

struct Point {
    var x: Int
    var y: Int
}
 var p1 = Point(x: 10, y: 20)
 var p2 = p1
 p2.x = 11
 p2.y = 21
 print("1111")

匯編觀察上面的值類型

mov    qword ptr [rip + 0x57e9], rax  0x1000019e7 + 0x57e9  0x1000071D0 p1的內(nèi)存地址
mov    qword ptr [rip + 0x57ea], rdx  0x1000019ee + 0x57ea  0x1000071D8

0x1000019ff <+79>:  mov    rax, qword ptr [rip + 0x57ca]  // 10
0x100001a06 <+86>:  mov    qword ptr [rip + 0x57d3], rax  // 0x1000071E0 p2的內(nèi)存地址
0x100001a0d <+93>:  mov    rax, qword ptr [rip + 0x57c4]  // 20
0x100001a14 <+100>: mov    qword ptr [rip + 0x57cd], rax  // 0x1000071E8
0x100001a1b <+107>: lea    rdi, [rbp - 0x18]

規(guī)律:局部變量的內(nèi)存格式 [rbp - 0x10],全局變量的內(nèi)存格式 [rip + 0x57e9]
全局變量:整個程序運(yùn)行過程中,只存在一份內(nèi)存;

值類型的賦值操作

栗子1

var s1 = "Jack"
var s2 = s1
s2.append("_rose")
print(s1)//Jack
print(s2)//Jack_rose

栗子2

var a1 = [1,2,3]
var a2=  a1
a2.append(4)
a1[0] = 2
print(a1)// [2,2,3]
print(a2)// [1,2,3,4]

栗子3

var d1 = ["max":10, "min":21]
var d2=  d1
d1["other"] = 7
d1["max"] = 12
print(d1)// ["other:12, ""max":10, "min":21]
print(d2)// ["max":12,"min": 21]

在swift標(biāo)準(zhǔn)庫中,為了提升性能,String檩帐、Array、Dictionary另萤、Set采用了Copy On Write.
比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作
對于標(biāo)準(zhǔn)庫值類型的賦值操作,Swift能確保最佳性能,所以沒必要為了保證性能來避免賦值.

struct Point {
    var x: Int
    var y: Int
}
var p1 = Point(x: 10,y: 20)
p1 = Point(x: 20,y: 30)

留意: 這個操作修改的還是p1原來的內(nèi)存.

引用類型

引用賦值給var 湃密、let 或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份(有點(diǎn)向快捷方式的概念),屬于淺拷貝.

class Point {
    var x: Int
    var y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}
func test() {
    var p1 = Point(x: 10,y: 20)
    p1 = p2
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诅挑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泛源,更是在濱河造成了極大的恐慌拔妥,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件达箍,死亡現(xiàn)場離奇詭異没龙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缎玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門硬纤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赃磨,你說我怎么就攤上這事筝家。” “怎么了邻辉?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵溪王,是天一觀的道長。 經(jīng)常有香客問我值骇,道長莹菱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任吱瘩,我火速辦了婚禮道伟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搅裙。我一直安慰自己皱卓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布部逮。 她就那樣靜靜地躺著娜汁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兄朋。 梳的紋絲不亂的頭發(fā)上掐禁,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音颅和,去河邊找鬼傅事。 笑死,一個胖子當(dāng)著我的面吹牛峡扩,可吹牛的內(nèi)容都是我干的蹭越。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼教届,長吁一口氣:“原來是場噩夢啊……” “哼响鹃!你這毒婦竟也來了驾霜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤买置,失蹤者是張志新(化名)和其女友劉穎粪糙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忿项,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓉冈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轩触。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寞酿。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脱柱,靈堂內(nèi)的尸體忽然破棺而出熟嫩,到底是詐尸還是另有隱情,我是刑警寧澤褐捻,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站椅邓,受9級特大地震影響柠逞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜景馁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一板壮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧合住,春花似錦绰精、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至僚害,卻和暖如春硫椰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萨蚕。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工靶草, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岳遥。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓奕翔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浩蓉。 傳聞我的和親對象是個殘疾皇子派继,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344