什么是被调用者和调用者保存的寄存器?

mug*_*tsu 40 assembly abi cpu-registers calling-convention

我在理解调用者和被调用者保存的寄存器之间的区别以及何时使用什么方面遇到了一些麻烦.

我使用的是MSP430:

程序:

mov.w #0,R7 
mov.w #0,R6 
add.w R6,R7 
inc.w R6 
cmp.w R12,R6 
jl l$loop 
mov.w R7,R12
ret
Run Code Online (Sandbox Code Playgroud)

上面的代码是被调用者,并且在教科书示例中使用,因此它遵循惯例.R6和R7被呼叫者保存,R12被呼叫者保存.我的理解是被调用者保存的regs不是"全局的",因为在过程中改变它的值不会影响它在程序之外的值.这就是您必须在开头将新值保存到被调用者注册表中的原因.

R12,保存的来电者是"全球性的",因为缺乏更好的词汇.该程序在通话后对R12产生持久影响.

我的理解是否正确?我错过了其他的东西吗?

nic*_*bie 89

呼叫者保存的寄存器(AKA易失性寄存器)用于保存不需要跨呼叫保留的临时量.

因此,如果要在过程调用后恢复此值,则调用者有责任将这些寄存器压入堆栈.

Callee保存的寄存器(AKA非易失性寄存器)用于保存应在调用之间保留的长期值.

当调用者进行过程调用时,可以预期这些寄存器在被调用者返回后将保持相同的值,使得被调用者有责任保存它们并在返回调用者之前恢复它们.

希望能帮助到你.

  • 我喜欢使用的另一个术语是"call-clobbered"与"call-preserved".调用者与被调用者只有一个字母不同,编译器实际上不会在调用之间保存/恢复调用被破坏的regs(他们只是将值放在调用保留的regs中.)volatile与non-volatile会导致与C的混淆. volatile`关键字.所以"call-clobbered"准确地描述了函数需要假设的*其他*函数,而不是它如何实现调用约定/ ABI. (6认同)

Chr*_*odd 13

Callee vs caller saved是一个约定,负责在整个调用中保存和恢复寄存器中的值.所有寄存器都是"全局"的,因为任何地方的任何代码都可以看到(或修改)寄存器,任何后来的代码都可以看到这些修改.寄存器保存约定的要点是代码不应该修改某些寄存器,因为其他代码假定该值未被修改.

在您的示例代码中,NONE的寄存器是被调用者保存,因为它不会尝试保存或恢复寄存器值.但是,它似乎不是一个完整的过程,因为它包含一个未定义标签的分支(l$loop).所以它可能是一个程序中间的代码片段,它将一些寄存器视为callee save; 你只是缺少保存/恢复说明.

  • @NAND:静态变量一般存储在内存中。在 MIPS 上,它们需要(暂时)加载到寄存器中才能使用,但通常“存活”在内存中 (2认同)

Pet*_*des 11

调用者保存/被调用者保存的术语是基于一个非常脑残的低效编程模型,调用者实际上会保存/恢复所有被调用破坏的寄存器(而不是在其他地方保留长期有用的值),而被调用者实际上会保存/restore 所有调用保留的寄存器(而不是只是不使用其中的一些或任何一个)。

或者你必须明白“调用者保存”的意思是“如果你以后想要这个值,就以某种方式保存”。

实际上,高效的代码可以让值在不再需要时被销毁。编译器通常会生成在函数开始时保存一些调用保留寄存器的函数(并在结束时恢复它们)。在函数内部,他们将这些 reg 用于需要跨函数调用生存的值。

我更喜欢 "call-preserved" vs. "call-clobbered",一旦你听说过基本概念,它们是明确的和自我描述的,不需要任何严肃的心理体操来从调用者的角度或被调用者的观点。(这两个术语都是从同一角度出发的)。

此外,这些术语相差不止一个字母。

易失性/非易失性这两个术语非常好,类似于存储是否会因断电而失去其价值(如 DRAM 与闪存)。但是 Cvolatile关键字具有完全不同的技术含义,因此在描述 C 调用约定时,这是“(非)-易失性”的缺点。


  • Call-clobbered,又名caller-savedvolatile寄存器适用于在下一次函数调用后不需要的临时/临时值。

从被调用者的角度来看,您的函数可以自由地覆盖(又名破坏)这些寄存器而无需保存/恢复。

从调用者的角度来看,call foo销毁(又名 ​​clobbers)所有被调用破坏的寄存器,或者至少您必须假设它确实如此。

您可以编写具有自定义调用约定的私有帮助函数,例如,您知道它们不会修改某个寄存器。但是,如果您只知道(或想要假设或依赖)目标函数遵循正常的调用约定,那么您必须将函数调用视为它确实破坏了所有调用破坏的寄存器。这就是名称的真正含义:调用破坏了这些寄存器。

一些进行过程间优化的编译器还可以使用自定义调用约定创建不遵循 ABI 的函数的仅供内部使用的定义。

  • Call-preserved,又名callee-saved非易失性寄存器在函数调用之间保持它们的值。这对于进行函数调用的循环中的循环变量很有用,或者一般来说,基本上是非叶函数中的任何内容。

从被调用者的角度来看,除非您将原始值保存在某处以便您可以在返回之前恢复它,否则无法修改这些寄存器。或者对于像堆栈指针这样的寄存器(几乎总是调用保留),您可以减去一个已知的偏移量并在返回之前再次添加它,而不是实际将旧值保存在任何地方。即您可以通过航位推算来恢复它,除非您分配运行时可变的堆栈空间量。然后通常您从另一个寄存器恢复堆栈指针。

一个可以从使用大量寄存器中受益的函数可以保存/恢复一些调用保留的寄存器,这样它就可以将它们用作更多的临时寄存器,即使它不进行任何函数调用。通常你只会在用完调用破坏的寄存器后才这样做,因为保存/恢复通常会在函数的开始/结束时花费一个推/弹出。(或者,如果您的函数有多个退出路径,则pop在每个路径中都有一个。)


命名为“来电者得救”是一个误导:你不要专门保存/恢复它们。通常,您将代码安排为具有在调用保留寄存器中或堆栈中的某个位置或您可以从中重新加载的其他位置的函数调用中幸存下来的值。让 acall破坏临时值是正常的。


ABI 或调用约定定义了哪些

例如,请参阅通过x86-64 System V ABI的 linux x86-64 函数调用保留哪些寄存器

此外,在我知道的所有函数调用约定中,arg 传递寄存器总是被调用破坏。请参阅rdi 和 rsi 调用者是否已保存或被调用者已保存寄存器?

但是系统调用调用约定通常使除返回值之外的所有寄存器都保留调用。(通常甚至包括条件代码/标志。)请参阅i386 和 x86-64 上的 UNIX 和 Linux 系统调用的调用约定是什么