美文网首页
从零学习Swift 04:汇编分析Struct 和 Class

从零学习Swift 04:汇编分析Struct 和 Class

作者: 小心韩国人 | 来源:发表于2020-04-16 20:35 被阅读0次
    总结

    一: 结构体

    在 Swift 标准库中,绝大多数的类型都是结构体类型,比如Bool,Int,Double,String,Array,Dictionary等等都是结构体类型,比如Int的定义如下:

    public struct Int : FixedWidthInteger, SignedInteger {
    ...
    }
    

    编译器会根据情况为所有结构体类型自动生成初始化器(initializer),有可能会生成多个初始化器.生成初始化器的原则是:保证所有成员都有值.

    比如:

    struct TestStruct{
        var a: Int
        var b: Int
    }
    // 编译器自动生成了传入a,b的初始化器
    let s = TestStruct(a: 10, b: 20)
    

    如果结构体的成员有初始值,那么编译器会给结构体生成多个初始化器.

    比如只有一个成员有初始值:

    struct TestStruct{
        var a: Int = 0
        var b: Int
    }
    
    // 生成了两个初始化器
    let s = TestStruct(a: 10, b: 20)
    let s2 = TestStruct(b: 20)
    

    两个成员都有初始化器:

    struct TestStruct{
        var a: Int = 0
        var b: Int = 0
    }
    
    //生成4个初始化器
    let s = TestStruct(a: 10, b: 20)
    let s2 = TestStruct(b: 20)
    let s3 = TestStruct(a: 10)
    let s4 = TestStruct()
    
    

    我们也可以给结构体自定义初始化器,但是一旦自定义初始化器后,编译器就不会再自动生成初始化器:


    自定义初始化器后,只有自定义的初始化器有效,系统不会再生成初始化器

    其实我们自定义的初始化器和系统自动生成的初始化器,本质是一模一样的,他们的底层汇编都是一样的.

    首先看一下编译器自动生成的初始化器的汇编:

    编译器自动生成的初始化器汇编

    自定义初始化器汇编:


    自定义初始化器汇编

    可以看到上面两幅图的汇编语言完全是一模一样的.

    结构体内存

    结构体的内存和有关联值的枚举类型很相似:


    结构体内存

    结构体中还可以定义方法:

    结构体中定义方法

    二:Class 类

    类的定义

    类的定义和结构体很相似,但是如果类的成员没有初始值,编译器不会为类生成初始化器:


    编译器没有为类生成初始化器

    如果类的成员都有初始值,编译器才会为类生成无参初始化器:


    所以如果类的成员没有初始值的话,就需要我们自定义初始化器了:


    自定义类的初始化器

    类和结构体的区别

    从上面可以看到结构体和类真的是非常的相似,那么结构体和类有什么区别呢?

    一:类型不同,它们的本质区别是:结构体是值类型,类是引用类型

    
    //类
    func test(){
    class Person{
        var name:String
        var age: Int
        
        init(){
            self.name = "Jack"
            self.age = 18
        }
      }
    
    let person = Person()
    
    
    //结构体
    struct Point {
        let x: Int
        let y: Int
      }
        let point = Point(x: 10, y: 20)
    }
    
    

    如上所示的代码,因为point 和 person都是在test()函数内部定义的.所以它们的内存布局如下:

    解读:变量point , person的内存空间都在栈空间.不同的是,Struct的数据也存储在变量的内存中.而Class类型的变量内存中存储的是一个地址,一个指向堆空间类对象的内存地址.Class类型的数据都存储在堆空间中.

    我们可以打个断点,看看point 变量和 person 变量的内存是不是如上图分析的那样,要搞清楚变量有没有分配堆空间内存,一个很重要的指标就是是否调用了malloc方法,因为这个方法是向堆空间申请内存的.我们在let person = Person()打上断点,然后一步步执行,会发现最终会调用malloc方法:

    Class对象向堆空间申请内存

    而在let point = Point(x: 10, y: 20)打断点,可以看到Point的初始化代码的汇编简洁如下,根本就没有调用malloc代码:

    Struct 初始化没有向堆空间申请内存

    还可以直接打印出point变量和person变量的内容,更直观一些:

    point 和 person 变量内存中的内容
    • 如果想查看一个类型所创建出来的变量占用多少内存,可以使用MemoryLayout<类型>.stride查看,比如:
      查看String类型对象占用多少内存:MemoryLayout<String>.stride
      查看Int类型对象占用多少内存:MemoryLayout<Int>.stride

    • 如果想查看创建出来的指针变量在堆空间占用多少内存,可以使用malloc_size(ptr: UnsafeRawPointer)

    二:赋值操作不同,值类型是深拷贝,引用类型是浅拷贝(拷贝内存地址)

    2.1 值类型的深度拷贝

    看看struct类型赋值操作的汇编语言:

    //值类型的赋值操作
    func testValueType(){
        
        struct Point{
            var x: Int
            var y: Int
        }
        
        let a = Point(x: 10, y: 20)
        var b = a
        b.x = 11
        b.y = 21
        
        print("")
    }
    

    它的汇编如下:


    第一步:进入Point 的 init() 函数 第二步:Point 的 init() 函数 函数内部 第三步:深拷贝
    2.2 引用类型的浅拷贝

    我们把上面的struct改成class:

    
    func testReferenceType(){
    
        class Point{
            var x: Int
            var y: Int
            init(x: Int,y: Int){
                self.x = x
                self.y = y
            }
        }
    
        let a = Point(x: 10, y: 20)
        var b = a
        b.x = 11
        b.y = 21
    
        print("")
    }
    
    testReferenceType()
    
    

    它的汇编代码如下:


    引用类型赋值汇编代码

    上图中的rax存放的是Point初始化方法创建的对象地址,我们打印看一下:

    2.3值类型和引用类型的 let

    使用let修饰值类型或者引用类型的常量,它们的区别也是很大的.

    如图所示代码:


    因为point 和 person都是let修饰的,也就是说他们的内存是不允许修改的.
    它们的内存存储方式如下:

    相关文章

      网友评论

          本文标题:从零学习Swift 04:汇编分析Struct 和 Class

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