返回

函数内幕(上):bl和lr协同工作

IOS

在上一篇文章中,我们了解到当没有遇到bl指令时,lr寄存器和pc寄存器保存的都是即将执行的指令地址。但是,当遇到bl指令后,lr寄存器值就不再改变,直到遇到ret指令或另一条bl指令才会改变。lr寄存器可以理解为函数嵌套调用时返回上一级函数的路径,而pc寄存器只是简单指向下一条即将执行的指令地址。

bl指令:函数调用的关键

bl指令是汇编语言中用于函数调用的关键指令。当程序执行到bl指令时,它将执行以下操作:

  1. 将当前的pc寄存器值压入堆栈,即保存当前指令地址。
  2. 将bl指令后的立即数(偏移量)或寄存器值添加到pc寄存器,使pc寄存器指向要调用的函数的地址。
  3. 跳转到新的pc寄存器值所指向的地址,开始执行新的函数。

lr寄存器:返回地址的守护者

lr寄存器在函数调用中扮演着重要的角色,它保存着函数返回地址,以便在函数执行完成后能够返回到调用它的位置。当程序执行bl指令时,它将当前的pc寄存器值压入堆栈,同时将bl指令后的立即数(偏移量)或寄存器值添加到pc寄存器,使pc寄存器指向要调用的函数的地址。此时,lr寄存器中保存的地址就是调用函数的返回地址。

函数嵌套调用:lr寄存器的多重角色

当函数中嵌套调用其他函数时,lr寄存器将发挥更大的作用。当一个函数调用另一个函数时,当前函数的返回地址将被压入堆栈,lr寄存器中保存的地址将变为当前函数的返回地址。当嵌套函数执行完成后,它将通过lr寄存器中的返回地址返回到调用它的函数。这个过程可以一直重复,直到所有嵌套函数都执行完成,最终返回到最初调用的函数。

举个例子:函数嵌套调用的实际应用

为了更好地理解函数嵌套调用和lr寄存器的作用,让我们来看一个具体的例子。假设我们有一个函数名为calculate_average(),它用于计算一组数字的平均值。这个函数调用另一个函数名为get_numbers(),用于获取数字列表。get_numbers()函数又调用另一个函数名为sum_numbers(),用于计算数字列表的总和。

calculate_average():
    bl get_numbers()  // 调用get_numbers()函数
    bl sum_numbers()  // 调用sum_numbers()函数
    // 计算平均值
    ret

get_numbers():
    // 获取数字列表
    bl sum_numbers()  // 调用sum_numbers()函数
    ret

sum_numbers():
    // 计算数字列表的总和
    ret

在这个例子中,当calculate_average()函数调用get_numbers()函数时,get_numbers()函数的返回地址(即calculate_average()函数的地址)将被压入堆栈,lr寄存器中保存的地址将变为get_numbers()函数的返回地址。当get_numbers()函数调用sum_numbers()函数时,sum_numbers()函数的返回地址(即get_numbers()函数的地址)将被压入堆栈,lr寄存器中保存的地址将变为sum_numbers()函数的返回地址。

当sum_numbers()函数执行完成后,它将通过lr寄存器中的返回地址返回到get_numbers()函数。get_numbers()函数执行完成后,它将通过lr寄存器中的返回地址返回到calculate_average()函数。最终,calculate_average()函数执行完成后,它将返回到最初调用的位置。

通过这个例子,我们可以看到lr寄存器在函数嵌套调用中发挥了至关重要的作用,它确保了函数能够正确地返回到调用它的位置。