__cdecl
调用约定说:调用者清理栈。
__stdcall
调用约定说:callee cleanup stack。
所以我尝试测试下面的代码:
#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)
应该输出混乱的字符。
答案 0 :(得分:9)
“清理堆栈”实际上意味着“调整堆栈指针,使其与参数和返回地址被压入堆栈时对堆栈指针所做的更改相匹配”。在您的示例中,清理堆栈是无关紧要的 - 您将获得相同的未定义行为,因为您尝试访问堆栈分配的变量,该变量被执行printf()
所需的一系列后续参数所覆盖
如果你只用C或C ++编写代码,你就不应该关心“谁清理堆栈”等确切的细节 - 这些细节只有在汇编时才是关键。在C和C ++中,只需确保在通过这些指针调用函数之前用正确的调用约定标记函数指针,编译器将完成其余的工作。
答案 1 :(得分:2)
您的错误与调用约定无关。 callee()
正在返回一个指向局部变量的指针,只要callee()
返回就会无效,并且您获得了垃圾数据。
答案 2 :(得分:1)
它不像您想象的那样工作,因为在这两种情况下,堆栈指针都在每个函数调用之间进行调整。区别在于谁做到了。
使用__stdcall约定,在函数返回调用者前调整堆栈指针(删除所有参数)。这节省了代码,因为它只在一个地方完成。
使用__cdecl约定,堆栈指针由调用代码调整,只需 >>从被调用函数返回。如果您的函数具有可变数量的参数,例如printf,则很难删除参数,因为每个gtcall点都可以有不同的数字。因此,调用功能必须进行调整。它将知道实际传递了多少参数。
答案 3 :(得分:0)
在任何一种情况下,行为都是未定义的。最有可能的是,对printf
的调用将重用callee
先前使用的堆栈空间。
答案 4 :(得分:0)
在__cclcl的情况下,堆栈调整在被调用函数返回时发生。当你的printf执行时,堆栈将被调整。