为什么使用错误的调用约定有时会起作用?

时间:2011-08-10 22:48:41

标签: c++ winapi callback calling-convention

我使用“StartServiceCtrlDispatcher”函数在windows中注册一个回调函数(称为ServiceMain),但我声明的回调函数是用错误的调用约定编译的。

事情是某些计算机上的 ,当应用程序从回调函数返回时,应用程序崩溃,但在其他计算机上,应用程序没有崩溃

现在,一旦我发现bug一切正常,但我只是不明白为什么在某些计算机上它能正常工作而不会崩溃?

谢谢! : - )

4 个答案:

答案 0 :(得分:5)

这些都是特定于Windows的,我们在这里不是标准的C ++。

签出documentation of StartServiceDispatcher它只有一个参数,并被声明为WINAPI,这反过来意味着__stcall调用约定。

对于独立函数,__stdcall是两个主要调用约定之一。另一个是__cdecl。机器代码级别差异只是谁恢复堆栈指针:__stdcall它是函数本身,而__cdecl它是调用代码。

当函数实际上是__stdcall但被调用就像它是__cdecl一样,情况是有两次尝试恢复堆栈指针:一个在函数出口处,一个在调用代码中。函数中的那个将成功。根据调用代码中的尝试如何完成,它可以彻底解决问题(例如,如果只是添加所需的偏移量,将堆栈指针视为相对的),或者它可能没有任何有害影响。但它很可能造成混乱,因为关于从函数返回的堆栈指针值的假设是不正确的。

当函数实际为__cdecl时,它本身不会恢复堆栈指针,因为这是调用代码的责任。如果调用代码将其视为__stdcall,那么调用代码也不会恢复它,因为从调用代码的视图中,函数正在执行该操作。结果,如果你没有得到早期崩溃(因为假设破坏),那么重复的调用,比如循环,会占用堆栈空间。

这都是未定义的行为。

Undefined Behavior的一个属性是它可以做任何事情,包括显然有效......

干杯&第h。,

答案 1 :(得分:2)

调用约定的细节不同,例如保留哪些寄存器。如果你碰巧没有在那些寄存器中存储任何你需要的东西,那么当它们不必存在时它们就被删除并不重要。同样,如果你的调用约定不同于它如何处理返回值,如果你没有返回任何东西,那么它并不重要。

幸运的是,x64只有一个调用约定,而这整个混乱将会在过去。

答案 2 :(得分:1)

应用程序崩溃的计算机可能一直在使用.NET Framework第4版。

查看以下文章: http://msdn.microsoft.com/en-us/library/ee941656.aspx

它在互操作性 - 平台调用:

下声明了以下内容

“为了提高与非托管代码的互操作性能,平台调用中不正确的调用约定现在导致应用程序失败。在以前的版本中,编组层将这些错误解决了堆栈。”

答案 3 :(得分:0)

这与内存中的当前内容有关。我们假设您有两个这样的函数:

void stdcall f1(...) { ... }

void cdecl f2(...) { ... }

stdcall是Windows调用约定,而大多数编译器使用cdecl。它们之间的区别在于谁有责任在通话结束后清除堆栈。在stdcall中,被叫方(f1f2)在cdecl中执行了此操作。

毕竟,堆栈中充满了未知值。因此,当它被清理(错误地)时,您在堆栈中访问的下一个值是不确定的。它很可能是一个可接受的值,或者它可能是一个非常糟糕的值。原则上,这是堆栈溢出(错误,而不是站点)的工作方式。