在 C 函数调用期间如何保存寄存器和其他信息?
How are registers and other information preserved during function calls in C?
假设我有三个函数,f1()
、f2()
和 f3()
。当 f1
被调用时,它将信息存储在 CPU 寄存器中(我想还有其他重要信息)。现在,根据编译时未知的条件,f1
将调用 f2
或 f3
。 f2
和 f3
使用非常不同的寄存器,其中一些可能与 f1
使用的寄存器重叠。以下推理正确吗?
编译器知道特定函数在执行期间需要哪些寄存器。因此,当 f1
调用 f2
或 f3
时,函数调用代码会保留那些 f2
或 f3
在堆栈上使用的寄存器,无论是否不是 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/
几个值得注意的原则:
堆栈帧
调用方(调用 f1 的函数)和被调用方函数(您的 f1、f2... 函数)
易失性和非易失性寄存器。对于您的问题,您只需要担心非易失性寄存器。
每个函数都有一个栈帧,这是栈的一个可扩展块,临时存储需要加载和加载出寄存器的数据。
在每次函数调用(从调用者到被调用者)之前,您希望传递的值,即您的参数,将被放置在一些预定的寄存器中(通常为 4-6 个,具体取决于编译器 – 请参阅link);如果参数多于预定寄存器的数量,那么这些额外的值将存储在堆栈中(通常是调用者堆栈帧)。
如果调用者正在使用这些预先指定的寄存器,那么在调用被调用者(例如您的 f1 函数)之前,编译器会将这些值压入调用者的堆栈帧,然后再将参数分配给寄存器。一旦被调用函数(callee)returns,这些值就从栈中恢复到各自的寄存器中。
当编译器将您的 C 代码转换为 assembly/opcode.
时,调用同一系统的一系列函数的调用方式或顺序无关紧要。
假设我有三个函数,f1()
、f2()
和 f3()
。当 f1
被调用时,它将信息存储在 CPU 寄存器中(我想还有其他重要信息)。现在,根据编译时未知的条件,f1
将调用 f2
或 f3
。 f2
和 f3
使用非常不同的寄存器,其中一些可能与 f1
使用的寄存器重叠。以下推理正确吗?
编译器知道特定函数在执行期间需要哪些寄存器。因此,当 f1
调用 f2
或 f3
时,函数调用代码会保留那些 f2
或 f3
在堆栈上使用的寄存器,无论是否不是 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/
几个值得注意的原则:
堆栈帧
调用方(调用 f1 的函数)和被调用方函数(您的 f1、f2... 函数)
易失性和非易失性寄存器。对于您的问题,您只需要担心非易失性寄存器。
每个函数都有一个栈帧,这是栈的一个可扩展块,临时存储需要加载和加载出寄存器的数据。
在每次函数调用(从调用者到被调用者)之前,您希望传递的值,即您的参数,将被放置在一些预定的寄存器中(通常为 4-6 个,具体取决于编译器 – 请参阅link);如果参数多于预定寄存器的数量,那么这些额外的值将存储在堆栈中(通常是调用者堆栈帧)。
如果调用者正在使用这些预先指定的寄存器,那么在调用被调用者(例如您的 f1 函数)之前,编译器会将这些值压入调用者的堆栈帧,然后再将参数分配给寄存器。一旦被调用函数(callee)returns,这些值就从栈中恢复到各自的寄存器中。
当编译器将您的 C 代码转换为 assembly/opcode.
时,调用同一系统的一系列函数的调用方式或顺序无关紧要。