Căt*_*rbu 7 c x86 cdecl variadic-functions calling-convention
有一些调用约定(例如pascal,stdcall),但就我而言,C 确实使用cdecl(C 声明)。这些约定中的每一个在调用者将参数加载到堆栈上的方式上都略有不同,分别是由哪个(调用者/被调用者)进行清理。
谈到清理,这是我的问题。我不明白:有三种不同的东西吗?
或者我应该怎么看他们?
此外,这个问题的目标基本上是可变参数函数如何在像 Pascal 这样的调用约定中工作,或者stdcall被调用者应该在哪里清除/清理/恢复(我不知道哪个操作)堆栈 - 但他不知道有多少参数它会收到。
编辑
为什么将参数压入堆栈的顺序如此重要?您仍然拥有第一个参数(不是来自省略号的稳定参数),它为您提供有关 - 例如 - 变量参数数量的信息。并且还有“监护人”,它可以添加到省略号标点符号中,并且可以用作独立于调用约定的变量部分结束的标记。在这个链接中,如果调用者和被调用者在搞乱它们之前都保存了它们的状态,那么为什么调用者和被调用者都应该恢复这些寄存器的值?不应该只有其中一个(例如调用者)在调用函数之前将它们保存在堆栈中,仅此而已?另外,在同一个链接上
“因此,堆栈指针 ESP 可能会上下移动,但 EBP 寄存器保持固定。这很方便,因为这意味着我们始终可以将第一个参数称为 [EBP + 8],而不管在功能。”
推送的变量和局部变量在内存中是连续的。使用 EBP 推荐他们的优势在哪里?即使堆栈大小发生变化,它们之间也永远不会有一些动态偏移。
我读过的材料之一是这个站点(只是开始),以便更好地了解堆栈帧到底是什么。然后我继续 yt 并找到了这些堆栈概述和调用堆栈教程,但他们不知何故错过了我需要的部分。当你调用函数时到底发生了什么(我不明白指令“调用地址”后跟下一个指令a push值到堆栈上,这意味着返回值)。谁来控制退货地址?呼叫者,召集者?被叫方?当被调用者返回时,程序继续执行一条指令,该指令是从寄存器中读取操作或什么?
就我而言,C确实使用
cdecl
不管它的名字如何,cdecl 约定对于 C 代码来说并不通用,甚至在 x86 架构上也是如此。它的优点是定义和实现简单,但不使用CPU寄存器进行参数传递,效率更高。即使在寄存器匮乏的 x86 上,这也会产生影响,但在具有更多可用寄存器的架构(例如 x86_64)上,这会产生更大的影响。
谈到清理,这是我的问题。我不明白:有三种不同的东西吗?
- 堆栈干净
- 将指针移回倒数第二个堆栈帧
- 堆栈恢复
或者我应该如何看到他们?
我倾向于将(1)和(3)解释为同一事物的不同表达方式,但可以想象有人会对它们进行区分。(3)及相关措辞是我最常遇到的。(2)不一定是同一件事,因为可能有两个相关的堆栈参数需要恢复:堆栈帧的基址(见下文)和堆栈的顶部。如果堆栈帧包含的信息多于参数和局部变量值(例如前一个堆栈帧的基址),则堆栈帧基址非常重要。
另外,这个问题的目标基本上是可变参数函数如何在 Pascal 或 stdcall 等调用约定中工作,其中被调用者应该清除/清理/恢复(我不知道哪个操作)堆栈 - 但他不知道有多少它将收到的参数。
堆栈不一定是全貌。
如果被调用者不知道如何找到调用者堆栈的顶部,以及(如果需要)调用者堆栈帧的底部,则它无法恢复堆栈。但实际上,这通常是硬件辅助的。
以 x86(cdecl 为其设计)为例,CPU 具有堆栈(帧)基址和当前堆栈指针的寄存器。调用者的堆栈基址存储在堆栈上距被调用者堆栈基址已知的偏移量 (0) 处。无论参数的数量如何,被调用者都会通过将堆栈顶部移动到自己的堆栈基址并弹出那里的值以获得调用者的堆栈基址来恢复堆栈。
然而,可以想象的是,在某处使用了一种调用约定,除了一次弹出一个元素之外,无法将堆栈恢复到选定的先前状态,并且不会显式地将参数数量传递给被调用的元素。函数,并且需要被调用者恢复调用者的堆栈。这样的调用约定不支持可变参数函数。
为什么参数压入堆栈的顺序如此重要?
从一般意义上讲,顺序并不重要,但对于可能单独编译的调用者和被调用者来说,达成一致至关重要。否则,被调用者无法将传递的值与其预期的参数相匹配。因此,无论调用约定在多大程度上依赖于堆栈,它都必须精确指定向那里传递哪些参数以及按什么顺序传递。
关于堆栈框架:这是 C 未指定的更多材料,并且至少在某种程度上有所不同。但从概念上讲,函数调用的堆栈帧是为该调用提供执行上下文的堆栈部分。它通常为局部变量提供存储,并且可能包含附加信息,例如返回地址和/或调用者的堆栈帧指针的值。它还可能包含适合执行环境的其他每个函数调用的信息。详细信息是所使用的调用约定的一部分。
| 归档时间: | 
 | 
| 查看次数: | 341 次 | 
| 最近记录: |