美文网首页
从零学习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