美文网首页
汇编分析结构体、类的内存布局

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

作者: AndyYaWei | 来源:发表于2020-08-27 22:51 被阅读0次

    结构体

    先看一下简单的语法书写
    栗子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<Point>.size)
        print(MemoryLayout<Point>.stride)
        print(MemoryLayout<Point>.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<Size>.stride",MemoryLayout<Size>.stride)
        print("MemoryLayout<Point>.stride",MemoryLayout<Point>.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<Size>.stride 8
    MemoryLayout<Point>.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<Size>.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
    }
    

    相关文章

      网友评论

          本文标题:汇编分析结构体、类的内存布局

          本文链接:https://www.haomeiwen.com/subject/zkkerktx.html