【问题标题】:Confused about __cdecl and __stdcall calling convention对 __cdecl 和 __stdcall 调用约定感到困惑
【发布时间】:2011-03-13 02:10:38
【问题描述】:

__cdecl 调用约定说:调用者清理堆栈。

__stdcall 调用约定说:被调用者清理堆栈。

所以我尝试测试下面的代码:

#include <...>

char *callee()
{
    char str[] = "abcd";

    return str;
}

int main()
{
    char *str;
    str = callee();
    printf("%s\n", str);

    return 0;
}

根据上面的两个调用约定,我认为:

__cdecl 调用者清理堆栈,所以printf("%s\n", str) 应该输出“abcd”。

__stdcall 被调用者清理堆栈,所以printf("%s\n", str) 应该输出混乱的字符。

但实际上,两个都输出杂乱的字符。我很困惑。感谢您的帮助!

【问题讨论】:

  • str 在堆栈上,当callee 完成时,str 完成。

标签: c++ c


【解决方案1】:

“清理堆栈”实际上是指“调整堆栈指针,使其匹配在参数和返回地址被压入堆栈时对堆栈指针所做的更改”。在您的示例中,谁清理堆栈无关紧要-您将获得相同的未定义行为,因为您尝试访问堆栈分配的变量,该变量被执行printf()所需的一系列稍后推送的参数覆盖。

如果您只用 C 或 C++ 编写代码,则不应关心诸如“谁清理堆栈”之类的确切细节——这些细节仅在以汇编语言编写时才至关重要。在 C 和 C++ 中,只需确保在通过这些指针调用函数之前使用正确的调用约定标记函数指针,编译器将完成其余的工作。

【讨论】:

  • main()函数中,我可以这样做:const char* &amp;str = callee();,即绑定一个const引用到callee()的返回值?
  • @Alcott:不,因为callee() 返回一个指针,它的生命周期与它指向的缓冲区的生命周期决不同步。
  • 你说,局部变量char str[] = "abcd";是在栈上分配的,当callee()完成后,它的栈被回收,所以char str[]就消失了?
  • @Alcott:是的,它是在自动存储中分配的,当函数退出时,它会超出范围,无法再合法访问。
【解决方案2】:

您的错误与调用约定无关。 callee() 正在返回一个指向局部变量的指针,该指针在 callee() 返回时立即无效,并且您会得到垃圾数据。

【讨论】:

    【解决方案3】:

    它不像你想象的那样工作,因为在这两种情况下,堆栈指针都会在每个函数调用之间进行调整。区别只是是谁做的。

    使用 __stdcall 约定,在函数返回调用者之前调整堆栈指针(删除所有参数)。这样可以节省代码,因为它只在一个地方完成。

    使用 __cdecl 约定,堆栈指针由调用代码调整,就在被调用函数返回之后。如果您的函数具有可变数量的参数,例如 printf,则很难删除参数,因为 gtcall 的每个点都可以有不同的数量。因此,调用函数将不得不进行调整。它会知道实际传递了多少参数。

    【讨论】:

      【解决方案4】:

      在任何一种情况下,行为都是未定义的。最有可能的是,对printf 的调用将重用callee 之前使用的堆栈空间。

      【讨论】:

        【解决方案5】:

        在 __cdecl 情况下,堆栈调整发生在被调用函数返回时。到您的 printf 执行时,堆栈将已被调整。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-10-13
          • 2020-10-17
          • 2020-11-21
          • 1970-01-01
          • 2017-03-22
          • 2021-11-28
          • 1970-01-01
          相关资源
          最近更新 更多