C 调用约定 : who cleans the stack in variadic functions vs normal functions?

标签 c x86 variadic-functions calling-convention cdecl

有一些调用约定(例如 pascalstdcall )但就我而言,C 确实使用 cdecl (C 声明)。这些约定中的每一个在调用者将参数加载到堆栈上的方式上都略有不同,分别由哪个(调用者/被调用者)执行。清理 .
谈到清理,这是我的问题。我不明白:有三种不同的东西吗?

  • 堆栈清洁
  • 将指针移回倒数第二个堆栈帧
  • 堆栈恢复

  • 或者我应该怎么看他们?
    此外,这个问题的目标基本上是可变参数函数如何在调用约定如 Pascal 或 stdcall 中起作用。被调用者应该清除/清理/恢复(我不知道哪个操作)堆栈的位置-但他不知道它将接收多少个参数。
    编辑
    为什么将参数压入堆栈的顺序如此重要?您仍然有第一个参数(不是来自省(introspection)略号的稳定参数),它为您提供有关 - 例如 - 变量参数数量的信息。还有一个“守护者”可以添加到省略号标点符号中,并且可以用作变量部分结束的标记,独立于调用约定。 In this link如果调用者和被调用者都在弄乱它们之前保存状态,为什么调用者和被调用者都应该恢复这些寄存器的值?不应该只有其中一个(例如调用者)在调用函数之前将它们保存在堆栈中,仅此而已?另外,在同一个链接上

    "So, the stack pointer ESP might go up and down, but the EBP register remains fixed. This is convenient because it means we can always refer to the first argument as [EBP + 8] regardless of how much pushing and popping is done in the function."


    推送变量和局部变量在内存中是连续的。使用 EBP 推荐他们的优势在哪里?即使堆栈大小发生变化,它们之间也永远不会有一些动态偏移。
    我读过的 Material 之一是this site (只是开始)以便更好地了解堆栈帧的确切含义。
    然后我继续前进并找到了这些stack overviewcall stack教程,但他们不知何故错过了我需要的部分。 What does exactly happends when you call the function (我不明白指令“调用地址”后面跟着下一条指令 a push 堆栈上的值,这意味着返回值)。谁控制退货地址?调用者,召集者?被调用者?当被调用者返回时,程序继续执行一条指令,该指令是从寄存器读取操作还是什么?

    最佳答案

    as far as I am concerned, C does use cdecl


    尽管有它的名字,cdecl 约定对于 C 代码并不通用,甚至在 x86 架构上也不通用。它的优点是定义和实现简单,但它不使用 CPU 寄存器进行参数传递,效率更高。即使在寄存器匮乏的 x86 上也会有所不同,但在具有更多可用寄存器的架构(例如 x86_64)上会产生更大的差异。

    Talking about the cleanup, here is my question. I do not understand: are there three different things?

    1. stack clean
    2. moving the pointer back to the penultimate stack frame
    3. stack restoration

    Or how should I see them?


    我倾向于将 (1) 和 (3) 解释为说同一件事的不同方式,但可以想象有人会对它们进行区分。 (3) 和相关的措辞是我最常遇到的。 (2)不一定是一样的东西,因为可能有两个相关的栈参数需要恢复:栈帧的底(见下),和栈顶。如果堆栈帧包含比参数和局部变量值更多的信息,例如前一个堆栈帧的基数,则堆栈帧基数很重要。

    Also, the target of this question is basically how could variadic function works in calling conventions like Pascal or stdcall where the callee should clear / clean / restore (I don't know which operation) the stack - but he doesn't know how many parameters it will receive.


    堆栈不一定是全貌。
    如果被调用者不知道如何找到其调用者的栈顶,以及必要时,它的调用者的栈帧的基,则被调用者无法恢复栈。但在实践中,这通常是硬件辅助的。
    以 x86(为其设计了 cdecl)为例,CPU 具有堆栈(帧)基址和当前堆栈指针的寄存器。调用者的堆栈基存储在距被调用者堆栈基的已知偏移量 (0) 的堆栈上。不管参数的数量是多少,被调用者通过将栈顶移动到它自己的栈底来恢复栈,并从那里弹出值以获得调用者的栈底。
    然而,可以想象,在某个地方使用了一个调用约定,除了一次弹出一个元素之外,无法将堆栈恢复到选定的先前状态,它没有明确地将参数的数量传达给被调用的函数,这需要被调用者恢复调用者的堆栈。这样的调用约定不支持可变参数函数。

    Why is it so important the order in which parameters are pushed on to the stack?


    顺序在任何一般意义上都不重要,但是对于可以单独编译的调用者和被调用者来说,就它达成一致是必不可少的。否则,被调用者无法将传递的值与其预期的参数匹配。因此,无论调用约定在多大程度上依赖于堆栈,它都必须精确地指定在那里传递的参数以及传递的顺序。
    关于堆栈帧:这是 C 未指定的更多 Material ,并且至少在某种程度上有所不同。但是,从概念上讲,函数调用的堆栈帧是为该调用提供执行上下文的堆栈部分。它通常为局部变量提供存储空间,并且可能包含附加信息,例如返回地址和/或调用者的堆栈帧指针的值。它还可能包含适用于执行环境的其他每个函数调用信息。详细信息是使用中的调用约定的一部分。

    关于C 调用约定 : who cleans the stack in variadic functions vs normal functions?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64641240/

    相关文章:

    c - linux 内核代码 ">>"运算符

    每 32 字节复制一个二进制文件 32 字节

    assembly - 'push imm'如何编码?

    c - 为什么在使用 __VA_ARGS__ 从宏调用函数时 "vsprintf"会卡住?

    c - 如何将可变数量的参数传递给 sscanf?

    c - C 函数中的可选参数

    c - 全局标志恢复到 C 中的原始逻辑

    c - 为什么这个引导加载程序只打印 'S'

    c - kill函数是同步的吗?

    java - 如何使用可变参数和反射