在 C 函数调用期间如何保存寄存器和其他信息?

How are registers and other information preserved during function calls in C?

假设我有三个函数,f1()f2()f3()。当 f1 被调用时,它将信息存储在 CPU 寄存器中(我想还有其他重要信息)。现在,根据编译时未知的条件,f1 将调用 f2f3f2f3 使用非常不同的寄存器,其中一些可能与 f1 使用的寄存器重叠。以下推理正确吗?

编译器知道特定函数在执行期间需要哪些寄存器。因此,当 f1 调用 f2f3 时,函数调用代码会保留那些 f2f3 在堆栈上使用的寄存器,无论是否不是 f1.

使用它们

或者编译器是否有一些其他机制来保留寄存器,以便返回的函数不会丢失其数据?

"The compiler knows which registers a particular function needs during its execution."

不会,一般不会知道这个。

出于一个原因,函数可能来自编译器一无所知的(第三方)库。出于另一个原因,如果该函数调用另一个函数和另一个 etetera 怎么办?

编译器只会将所有 "suspect" 寄存器压入堆栈并在返回前弹出它们。

回想一下,编程语言是文档中的规范。对于 C11, read n1570.

Registers 在 C 中不存在(换句话说,几乎过时的 register 关键字不再与处理器寄存器相关)。它们只在机器代码中起作用(通常由 C 编译器 生成)。

但是,给定编译器(针对给定指令集和目标系统)生成的代码遵守某些约定,特别是 calling conventions and the ABI (read the system V x86-64 ABI governing Linux for an example). Thes conventions define how registers should be used, and which registers are callee-saved or caller-saved. Register allocation is a difficult part of an optimizing compiler 的工作。

编译器通常会发出代码将一些寄存器内容溢出到 call stack。并且给定的寄存器可以用于多种用途(例如,如果它们出现在同一函数的不同位置,它可以保留两个不同的变量)。

一般来说,调用约定不依赖于被调用函数(回想一下,您可以通过函数指针进行间接调用),但主要是它的签名。

我认为正如其他人所说,函数的参数通常通过多个寄存器向下发送(之后在堆栈上)。使用哪些寄存器取决于编译器——对于 gcc 参见 GNU C/assembler:http://cs.lmu.edu/~ray/notes/gasexamples/

几个值得注意的原则:

  1. 堆栈帧

  2. 调用方(调用 f1 的函数)和被调用方函数(您的 f1、f2... 函数)

  3. 易失性和非易失性寄存器。对于您的问题,您只需要担心非易失性寄存器。

每个函数都有一个栈帧,这是栈的一个可扩展块,临时存储需要加载和加载出寄存器的数据。

在每次函数调用(从调用者到被调用者)之前,您希望传递的值,即您的参数,将被放置在一些预定的寄存器中(通常为 4-6 个,具体取决于编译器 – 请参阅link);如果参数多于预定寄存器的数量,那么这些额外的值将存储在堆栈中(通常是调用者堆栈帧)。

如果调用者正在使用这些预先指定的寄存器,那么在调用被调用者(例如您的 f1 函数)之前,编译器会将这些值压入调用者的堆栈帧,然后再将参数分配给寄存器。一旦被调用函数(callee)returns,这些值就从栈中恢复到各自的寄存器中。

当编译器将您的 C 代码转换为 assembly/opcode.

时,调用同一系统的一系列函数的调用方式或顺序无关紧要。