前言
上一篇文章我们已经探索过了CPU与寄存器的一些原理和内在联系。我们知道任何高级语言方法的执行,他们的底层都是调用函数方法。但有没有想过函数的底层是怎么实现的呢?函数的调用一定涉及到了函数的调用栈,什么又是函数调用栈道呢?
栈和堆
![图片[1]-汇编探索(三)-一一网](https://www.proyy.com/skycj/data/images/2021-08-14/c88b810159fb7aa2c9cf1c14d59076c2.jpg)
什么是栈?栈是在内存中一种具有特殊访问方式的存储空间。遵循后进先出( 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转换的。



![[avas] AVAudioSession.mm:1080:-[AVAudioSession setActive:withOptions:error:]-一一网](https://www.proyy.com/wp-content/uploads/2024/10/load.gif )


















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)