对于子例程调用,我们保存pc的内容,以便重新启动调用例程。但是,如果被调用的子例程改变了通用寄存器的值,会发生什么呢?如果调用子例程必须访问存储在寄存器中的旧值,它不会对调用子例程造成任何问题吗?
发布于 2016-01-19 03:20:13
但是如果被调用的子例程改变了通用寄存器的值,会发生什么呢?
这取决于子例程修改哪些寄存器。根据calling convention的不同,存在一个寄存器列表,根据合同,子例程不得修改这些寄存器(另一个寄存器列表,该子例程可以自由修改)。
如果一个子例程没有遵守这个约定并修改了它不应该有的寄存器,那么糟糕的事情就会发生。
如果子例程想要使用它有义务不修改的寄存器,它必须首先将这些寄存器值保存到堆栈中。一旦保存了寄存器值,它就可以使用寄存器来存储新值。当子例程完成时,它必须使用堆栈上保存的值来恢复原始寄存器值。这样,子例程可以随心所欲地使用寄存器,但对于调用方来说,没有对寄存器进行可观察到的修改。
如果调用子例程必须访问存储在寄存器中的旧值,它不会对调用子例程造成任何问题吗?
只要子例程遵循调用约定,就不会。如果子例程不这样做,它会破坏(或“破坏”)“保留的”寄存器中的原始值,那么是的,它会导致问题。
然而,并不是所有的寄存器都必须保留。根据调用约定,子例程可以修改某些寄存器。如果这些寄存器对调用方很重要,则调用方必须在调用子例程之前将这些寄存器保存到堆栈,然后在调用子例程之后使用堆栈恢复寄存器值。
发布于 2016-01-19 03:14:53
有两个相互冲突的需求:
如果调用者必须保存/恢复它想要保留的所有内容,或者被调用者必须保存/恢复它使用的每个寄存器,那么它将变得很慢。
冲突需求的解决方案是使用 来定义哪些寄存器被调用保存,以及哪些寄存器可以被重击。未保留调用的寄存器可能不会被调用的特定函数破坏,但调用者必须假定它们是。
相关:Why not store function parameters in float registers?我的答案是考虑参数传递寄存器过多和调用保留寄存器不足之间的权衡。
在循环中调用另一个函数的函数通常会将其循环计数器和其他一些内容保存在调用保留寄存器中。被调用的函数要么根本不使用寄存器,要么已经保存/恢复了它。
如果调用函数的状态比调用保留的寄存器多,它必须将其中的一些状态“溢出”到内存中(即保存/恢复)。理想情况下,它可以溢出未修改的值,这些值在调用之前不需要保存,只需要在调用之后重新加载(例如,非静态数组或结构的基地址)。这比将内存的往返放入依赖链中的循环计数器更有效率。(如果被调用的函数只需要几个周期,但不能内联,因为它是单独编译的,这一点很重要。它还可以简单地保存指令/代码大小。)
x86有很多different calling conventions。有关链接,请参阅x86 tag wiki。对于这个主题,Agner Fog有一个很好的指南。
在x86-64linux ABI (由SystemV和OS X使用)中:
xmm0-15、
r11,它不用于传递任何内容,因此可以用作包装器/填充函数的临时注册表。)如果您愿意,也可以创建不符合ABI的函数,其中调用者知道被调用函数的实际寄存器。编译器可以在使用gcc -fwhole-program或链接时间优化等选项时做到这一点。通常,编译器总是生成符合ABI的函数,因为它们不确定它们发出的定义是不是在链接时使用的定义。显然,手工编写的ASM可以做任何事情,但是除了一小部分函数之外,手工做任何事情都是维护的噩梦。
https://stackoverflow.com/questions/33930622
复制相似问题