前言
上一篇文章我们已经探索过了CPU与寄存器的一些原理和内在联系。我们知道任何高级语言方法的执行,他们的底层都是调用函数方法。但有没有想过函数的底层是怎么实现的呢?函数的调用一定涉及到了函数的调用栈
,什么又是函数调用栈道呢?
栈和堆
什么是栈
?栈
是在内存中一种具有特殊访问方式的存储空间。遵循后进先出
( Last In Out Firt)LIFO。栈的开口对iOS系统来说来说是高地址往低地址走。局部变量
、参数
等这些在代码中写死的东西是放在栈空间。
- SP和FP寄存器
sp寄存器
:在任意时刻会保存我们栈顶的地址。
fp寄存器
:也称为x29
寄存器属于通用寄存器,但是在某些时刻我们利用它保存栈底的地址!
sp
保存栈顶(开口),fp
保存栈低。栈空间的开辟是通过栈的拉升sub
获取。
sub sp, sp, #0x40 ;
// 拉伸0x40(64字节)空间
stp x29, x30, [sp, #0x30] ;
// x29\x30 寄存器入栈保护
add x29, sp, #0x30 ;
// x29指向栈帧的底部
...
ldp x29, x30, [sp, #0x30] ;
// 恢复x29/x30 寄存器的值
add sp, sp, #0x40 ;
// 栈平衡
ret
复制代码
从上面汇编代码可以看到栈空间操作,以及栈平衡
。那什么是栈平衡
呢?栈是往低地址走,通过sub
减可以将地址开辟出来,通过add
指令加可以将栈地址恢复这个过程称为栈平衡。
- 内存指令
str
、ldr
都是操作栈空间指令。stp
指令是同时写入两个数据。当写数据时不能直接往sp寄存器写入数据,需要先拉伸栈空间。读/写 数据是都是往高地址读/写。str
(store register)指令:将数据从寄存器中读出来,存到内存中。ldr
(load register)指令:将数据从内存中读出来,存到寄存器中。ldr
和 str
的变种ldp
和stp
还可以操作2个寄存器。
sub sp, sp, #0x20 ;
// 拉伸栈空间32个字节
stp x0, x1, [sp, #0x10] ;
// sp往上加16个字节,存放x0 和 x1
ldp x1, x0, [sp, #0x10] ;
// 将sp偏移16个字节的值取出来,放入x1 和 x0
复制代码
上述汇编代码的用途其实是使用32
个字节空间作为这段程序的栈空间,然后利用栈将x0
和x1
的值进行交换。[ …]寻址 ,把左边的值存到右边地址里面去。
sp
寄存器就是一个指针一个标记,sp
操作必须16字节对齐,做8字节操作会崩溃。寄存的值进行交换其实内存地址没有变化。
bl
指令:将下一条指令的地址放入lr
(x30
)寄存器,转到标号处执行指令。
ret
指令:默认使用lr
(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址。ARM64平台的特色指令,它面向硬件做了优化处理的。x30
寄存器:x30
寄存器存放的是函数的返回地址。当ret
指令执行时刻,会寻找x30
寄存器保存的地址值。在函数嵌套调用的时候,需要讲x30
入栈。
- 函数的参数和返回值
ARM64
下,函数的参数是存放在x0
到x7
(w0
到w7
)这8个寄存器里面的。如果超过8个参数,就会入栈。函数的返回值是放在x0
寄存器里面的。为了提高效率,oc方法参数最好不要超过6
个,函数不要超过8
个。定义一些数组、结构体来提高效率。系统汇编是由llvm
转换的。