美文网首页
Swift 中结构体的内存分布

Swift 中结构体的内存分布

作者: 游城十代2dai | 来源:发表于2020-09-15 20:21 被阅读0次

0x00 初始化

下面这段结构体的创建很稳, 手动写初始化器

func testStruct1() {
    struct Point {
        var x: Int 
        var y: Int 
        init() {
            x = 11
            y = 12
        }
    }
    
    var point = Point()
    point.x = 2
}

testStruct1()

比对一下, 下面这个编译器创建的初始化器和上面的本质上其实是一样的

func testStruct1() {
    struct Point {
        var x: Int = 11
        var y: Int = 12
    }
    
    var point = Point()
    point.x = 2
}

testStruct1()

会发现自定义的初始化器和编译器自动的初始化器内部实现是一样的, 都是将 0xb, 0xc 也就是立即数 11, 12 放入寄存器 rax 和 rdx, 最后将这两个值返回给 testStruct1 函数层的栈空间

看下汇编层(MacOS 上的汇编):

// 这是初始化器的内部汇编
SwiftTest`init() in Point #1 in testStruct1():
->  0x100000f50 <+0>:  pushq  %rbp
    0x100000f51 <+1>:  movq   %rsp, %rbp
    0x100000f54 <+4>:  xorps  %xmm0, %xmm0
    0x100000f57 <+7>:  movaps %xmm0, -0x10(%rbp)
    0x100000f5b <+11>: movq   $0xb, -0x10(%rbp)
    0x100000f63 <+19>: movq   $0xc, -0x8(%rbp)
    0x100000f6b <+27>: movl   $0xb, %eax
    0x100000f70 <+32>: movl   $0xc, %edx
    0x100000f75 <+37>: popq   %rbp
    0x100000f76 <+38>: retq

0x01 手动初始化器修改属性值

有一种情况就是我们给结构体的属性赋了初值, 但是想要写一个初始化器, 在里面再一次赋新值, 运行后发现同样是在初始化器先对属性进行赋初值, 然后再将相应的栈空间存放的值修改为自定义初始化器内的要修改的内容

汇编发现先把 0x8, 0xa 也就是立即数 8, 10 放入栈空间 -0x10(%rbp) 和 -0x8(%rbp) 中, 随后立即把 0xb, 0xc 放入栈空间 -0x10(%rbp) 和 -0x8(%rbp)

代码如下:

// 将断点放到 var point 
func testStruct1() {
    struct Point {
        var x: Int = 8
        var y: Int = 10
        init() {
            x = 11
            y = 12
        }
    }
    
    var point = Point()
    point.x = 2
}

testStruct1()

//   从断点处进入 init 的汇编
SwiftTest`init() in Point #1 in testStruct1():
->  0x100000f40 <+0>:  pushq  %rbp
    0x100000f41 <+1>:  movq   %rsp, %rbp
    0x100000f44 <+4>:  xorps  %xmm0, %xmm0
    0x100000f47 <+7>:  movaps %xmm0, -0x10(%rbp)
    0x100000f4b <+11>: movq   $0x8, -0x10(%rbp)
    0x100000f53 <+19>: movq   $0xa, -0x8(%rbp)
    0x100000f5b <+27>: movq   $0xb, -0x10(%rbp)
    0x100000f63 <+35>: movq   $0xc, -0x8(%rbp)
    0x100000f6b <+43>: movl   $0xb, %eax
    0x100000f70 <+48>: movl   $0xc, %edx
    0x100000f75 <+53>: popq   %rbp
    0x100000f76 <+54>: retq   

0x02 内存位置

以下面的代码为例, 结构体变量 pointtestStruct2 函数内, 也就是在栈空间上

func testStruct2() {
    struct Point {
        var x: Int
        var y: Int
    }

    var point = Point(x: 6, y: 7)
    
    point.x = 11
}

testStruct2()

PS: edi, esirdi, rsi 的不同表现, 其实 edi, esi 为四字节寄存器

看汇编层, 会发现, testStruct2 函数汇编 将 '0x6, 0x7' 放入了寄存器 rdi, rsi 中, 作为参数传递给 初始化器, 而 初始化器内汇编 只是将参数赋值给了 rax, rdx 寄存器(也就是存放了函数返回值的寄存器), 随后看 testStruct2 函数汇编 将寄存器 rax, rdx 给了栈空间寄存器 -0x10(%rbp), -0x8(%rbp)

正常我们的栈空间地址都是由高到底的, 这里看后赋值 -0x8(%rbp) 明显地址高于 -0x10(%rbp), 所以可以看出, 这两个栈空间分别是 8 字节空间, 这也符合了 Int 在 64bits 上的字节数, 两个连续的 8 字节构成了 point 结构体变量的内存, 其首地址为 -0x10(%rbp)

// testStruct2 函数汇编 箭头处 → 为断点
SwiftTest`testStruct2():
    0x100000f80 <+0>:  pushq  %rbp
    0x100000f81 <+1>:  movq   %rsp, %rbp
    0x100000f84 <+4>:  subq   $0x10, %rsp
    0x100000f88 <+8>:  xorps  %xmm0, %xmm0
    0x100000f8b <+11>: movaps %xmm0, -0x10(%rbp)
    0x100000f8f <+15>: movl   $0x6, %edi
    0x100000f94 <+20>: movl   $0x7, %esi
->  0x100000f99 <+25>: callq  0x100000fc0               ; init(x: Swift.Int, y: Swift.Int) -> Point #1 in SwiftTest.testStruct2() -> () in Point #1 in SwiftTest.testStruct2() -> () at main.swift:30
    0x100000f9e <+30>: movq   %rax, -0x10(%rbp)
    0x100000fa2 <+34>: movq   %rdx, -0x8(%rbp)
    0x100000fa6 <+38>: movq   $0xb, -0x10(%rbp)
    0x100000fae <+46>: addq   $0x10, %rsp
    0x100000fb2 <+50>: popq   %rbp
    0x100000fb3 <+51>: retq   

// 初始化器内汇编
SwiftTest`init(x:y:) in Point #1 in testStruct2():
->  0x100000fc0 <+0>:  pushq  %rbp
    0x100000fc1 <+1>:  movq   %rsp, %rbp
    0x100000fc4 <+4>:  movq   %rdi, %rax
    0x100000fc7 <+7>:  movq   %rsi, %rdx
    0x100000fca <+10>: popq   %rbp
    0x100000fcb <+11>: retq   

0x03 和类做一个比较

这是一段类和结构体的创建

func testStruct3() {
    class Size {
        var width = 3
        var height = 4
    }
    struct Point {
        var x: Int = 8
        var y: Int = 10
    }
    
    let size = Size()
    var point = Point()
    
    size.width = 10
    point.x = 11
}

testStruct3()

相应的汇编代码

SwiftTest`testStruct3():
    0x100000fd0 <+0>:   pushq  %rbp
    0x100000fd1 <+1>:   movq   %rsp, %rbp
    0x100000fd4 <+4>:   pushq  %r13
    0x100000fd6 <+6>:   subq   $0x58, %rsp
    0x100000fda <+10>:  xorps  %xmm0, %xmm0
    0x100000fdd <+13>:  movaps %xmm0, -0x20(%rbp)
    0x100000fe1 <+17>:  xorl   %eax, %eax
    0x100000fe3 <+19>:  movl   %eax, %ecx
->  0x100000fe5 <+21>:  movq   %rcx, %rdi
    0x100000fe8 <+24>:  movq   %rcx, -0x40(%rbp)
    0x100000fec <+28>:  callq  0x100001070               ; type metadata accessor for Size #1 in SwiftTest.testStruct3() -> () at <compiler-generated>
    0x100000ff1 <+33>:  movq   %rax, %r13
    0x100000ff4 <+36>:  movq   %rdx, -0x48(%rbp)
    0x100000ff8 <+40>:  callq  0x1000010b0               ; __allocating_init() -> Size #1 in SwiftTest.testStruct3() -> () in Size #1 in SwiftTest.testStruct3() -> () at main.swift:44
    0x100000ffd <+45>:  movq   %rax, %rcx
    0x100001000 <+48>:  movq   %rax, -0x50(%rbp)
    0x100001004 <+52>:  callq  0x1000010e0               ; init() -> Point #1 in SwiftTest.testStruct3() -> () in Point #1 in SwiftTest.testStruct3() -> () at main.swift:48
    0x100001009 <+57>:  movq   %rax, -0x20(%rbp)
    0x10000100d <+61>:  movq   %rdx, -0x18(%rbp)
    0x100001011 <+65>:  movq   -0x50(%rbp), %rax
    0x100001015 <+69>:  addq   $0x10, %rax
    0x100001019 <+73>:  leaq   -0x38(%rbp), %rcx
    0x10000101d <+77>:  movl   $0x21, %edx
    0x100001022 <+82>:  movq   %rax, %rdi
    0x100001025 <+85>:  movq   %rcx, %rsi
    0x100001028 <+88>:  movq   -0x40(%rbp), %rax
    0x10000102c <+92>:  movq   %rcx, -0x58(%rbp)
    0x100001030 <+96>:  movq   %rax, %rcx
    0x100001033 <+99>:  callq  0x100001a78               ; symbol stub for: swift_beginAccess
    0x100001038 <+104>: movq   -0x50(%rbp), %rax
    0x10000103c <+108>: movq   $0xa, 0x10(%rax)
    0x100001044 <+116>: movq   -0x58(%rbp), %rdi
    0x100001048 <+120>: callq  0x100001a84               ; symbol stub for: swift_endAccess
    0x10000104d <+125>: movq   $0xb, -0x20(%rbp)
    0x100001055 <+133>: movq   -0x50(%rbp), %rdi
    0x100001059 <+137>: callq  0x100001a90               ; symbol stub for: swift_release
    0x10000105e <+142>: movq   -0x50(%rbp), %rax
    0x100001062 <+146>: addq   $0x58, %rsp
    0x100001066 <+150>: popq   %r13
    0x100001068 <+152>: popq   %rbp
    0x100001069 <+153>: retq   

通过以上汇编可以看出, 类初始化的时候是使用 __allocating_init() 来初始化, 在 0x100000ffd 这一行汇编读取 rax 寄存器 (lldb) register read rax, 调试发现内存为 rax = 0x0000000102426d10 可以看出既不是栈空间也不是全局区, 那就在中间为堆区, 这个 rax 给了栈空间 -0x50(%rbp) , 而结构体首地址存储的为值, 且给了栈空间 -0x20(%rbp), -0x18(%rbp)

由此可以看出, 在函数 testStruct3sizepoint 为两个变量, 其中 size 存放的为 Size 对象堆空间地址, 而 point 直接就在栈空间存放值

也可以看出, 我们将结构体用 let 修饰的时候是不能修改属性值, 因为值的内存位置是固定的, 而类用 let 修饰的时候可以修改属性值, 因为类创建出来的对象在堆空间其内部的属性根据内部的控制权限所影响

相关文章

网友评论

      本文标题:Swift 中结构体的内存分布

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