汇编分析结构体、类的内存布局

结构体

先看一下简单的语法书写
栗子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()//正确

编译器干了啥?
编译器会根据情况,可能会为结构体生成生成多个初始化器,宗旨是:保证所有成员都有初始值.

下面的代码能编译通过吗?

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()//错误

窥看初始化器本质

栗子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完全等价

结构体的本质

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.size)
    print(MemoryLayout.stride)
    print(MemoryLayout.alignment)
}
testStruct()

打印结果:

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

类的定义和结构体是相似的,但是编译器并没有为类自动生成可以传入成员值的初始化器.

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)//错误

对比结构体

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)//正确

结构体和类的本质区别

结构体和枚举都是值类型
类是引用类型(指针类型)

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

问题:函数调用时,内存在哪里?
0x10000就是point结构体变量的内存地址
0x10008就是point结构体中y的内存地址
size是指针变量,占用8个字节,放在zhan里.


内存分布

注:上图指针针对的是64bit环境

对象的堆空间申请过程

怎么看有木有在堆空间呢?
在swift里面就看有木有调用alloc、malloc这些函数.

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中,创建类的实例对象,要向堆空间申请内存,大概流程如下:

  • 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.stride",MemoryLayout.stride)
    print("MemoryLayout.stride",MemoryLayout.stride)
    print("-------------------------")

    var s = Size()
    print("s变量的内存地址:",Mems.ptr(ofVal: &s))
    print("s变量的内存的内容:",Mems.memStr(ofVal: &s))
    print("-------------------------")

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

    var p = Point()
    print("p变量的内存地址:",Mems.ptr(ofVal: &p))
    print("p变量的内存的内容:",Mems.memStr(ofVal: &p))
}

打印结果:

MemoryLayout.stride 8
MemoryLayout.stride 16
-------------------------
s变量的内存地址: 0x00007ffeefbff4a0
s变量的内存的内容: 0x00000001006060e0
-------------------------
s所指向的内存地址: 0x00000001006060e0
s所指向的内存的内容: 0x0000000100008298 0x0000000200000002 0x0000000000000001 0x0000000000000002
-------------------------
p变量的内存地址: 0x00007ffeefbff480
p变量的内存的内容: 0x0000000000000003 0x0000000000000004
Program ended with exit code: 0

由此可见:
1、s变量的内存地址0x00007ffeefbff4a0、p变量的内存地址0x00007ffeefbff480相邻,相差16个字节;
2、0x00000001006060e0 这个就是堆空间对象的地址;
3、MemoryLayout.stride为8,s所指向的内存的内容为32?
4、如何知道一个对象创建出来占用多少堆空间地址?

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

小常识:在Mac、iOS中的malloc函数分配的内存大小总是16的倍数

通过class_getInstanceSize可以得知类的对象真正使用的内存大小

来一个问题: 结构体内存一定在栈里吗?
不一定,要看你的结构体变量在哪里定义的.

  • 如果结构体变量是在函数里定义,它的内存在栈空间.
    如:
    func test() {
        var p = Ponit()//结构体
    }
  • 如果结构体变量是在外边定义的,那它的内存就在数据段(全局区)是一个全局变量.
    如:
var p = Ponit()//结构体
class Size {
    var w = 1
    var h = 2
}
  • 如果结构体变量在一个类里面创建,那它的内存就在堆空间.
    如:
class Size {
    var w = 1
    var h = 2
    var p = Ponit()//结构体
}

思考:

class Size {
    var w = 1
    var h = 2
    func test() {
        var p = Ponit()//结构体
    }
}

上面这个结构体变量p内存地址在哪里?

联想一下: 类的内存在哪里呢?
无论在哪里创建对象,这个对象的内存一定在堆空间.只不过指针变量的内存在的位置不一定.函数里面定义就在栈空间,函数外边定义就在(数据段)全局区.

值类型

值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份(深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的内存地址
mov qword ptr [rbp - 0x8], rdx   //  rbp - 0x8   0x1008   

mov qword ptr [rbp - 0x20], rax  //  rbp - 0x20           p2的内存地址
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的内存地址
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的内存地址
0x100001a0d <+93>:  mov    rax, qword ptr [rip + 0x57c4]  // 20
0x100001a14 <+100>: mov    qword ptr [rip + 0x57cd], rax  // 0x1000071E8
0x100001a1b <+107>: lea    rdi, [rbp - 0x18]

规律:局部变量的内存格式 [rbp - 0x10],全局变量的内存格式 [rip + 0x57e9]
全局变量:整个程序运行过程中,只存在一份内存;

值类型的赋值操作

栗子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标准库中,为了提升性能,String、Array、Dictionary、Set采用了Copy On Write.
比如仅当有“写”操作时,才会真正执行拷贝操作
对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没必要为了保证性能来避免赋值.

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

留意: 这个操作修改的还是p1原来的内存.

引用类型

引用赋值给var 、let 或者给函数传参,是将内存地址拷贝一份(有点向快捷方式的概念),属于浅拷贝.

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
}

你可能感兴趣的:(汇编分析结构体、类的内存布局)