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 内存位置
以下面的代码为例, 结构体变量 point
在 testStruct2
函数内, 也就是在栈空间上
func testStruct2() {
struct Point {
var x: Int
var y: Int
}
var point = Point(x: 6, y: 7)
point.x = 11
}
testStruct2()
PS: edi, esi
是 rdi, 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)
由此可以看出, 在函数 testStruct3
中 size
和 point
为两个变量, 其中 size
存放的为 Size
对象堆空间地址, 而 point
直接就在栈空间存放值
也可以看出, 我们将结构体用 let
修饰的时候是不能修改属性值, 因为值的内存位置是固定的, 而类用 let
修饰的时候可以修改属性值, 因为类创建出来的对象在堆空间其内部的属性根据内部的控制权限所影响
网友评论