奇怪的MSC 8.0错误:“在函数调用中没有正确保存ESP的值......”

时间:2008-09-27 00:45:47

标签: c++ visual-c++ x86 stack calling-convention

我们最近试图将一些Visual Studio项目分解为库,并且一切似乎在一个测试项目中编译和构建,其中一个库项目作为依赖项。但是,尝试运行该应用程序给了我们以下令人讨厌的运行时错误消息:

  

运行时检查失败#0 - ESP的值未在函数调用中正确保存。这通常是调用使用不同调用约定声明的函数指针的结果。

我们甚至从未为函数指定调用约定(__cdecl等),使所有编译器开关都处于默认状态。我检查过,项目设置与整个库和测试项目中的调用约定是一致的。

更新:我们的一个开发人员将“基本运行时检查”项目设置从“两者(/ RTC1,等同于/ RTCsu)”更改为“默认”,运行时间消失,使程序运行正常。我完全不相信这一点。这是一个合适的解决方案,还是一个危险的黑客?

19 个答案:

答案 0 :(得分:47)

这个调试错误意味着在函数调用之后堆栈指针寄存器没有返回到它的原始值,即在函数调用之前推送的数量没有跟随<电话结束后,em> pops 。

我知道有两个原因(都是动态加载的库)。 #1是VC ++在错误消息中描述的内容,但我不认为这是导致错误的最常见原因(参见#2)。

1)不匹配的通话约定:

来电者和被叫者没有就谁将要做什么达成适当的协议。例如,如果您正在调用_stdcall的DLL函数,但由于某种原因,您在调用中将其声明为_cdecl(VC ++中的默认值)。如果您在不同的模块中使用不同的语言等,这种情况会发生很多。

你必须检查违规函数的声明,并确保它没有被声明两次,并且不同。

2)类型不匹配:

调用者和被调用者不使用相同的类型进行编译。例如,一个公共标题定义了API中的类型并且最近已经改变,并且一个模块被重新编译,但另一个模块没有 - 即。某些类型的调用者和被调用者可能具有不同的大小。

在这种情况下,调用者会推送一个大小的参数,但被调用者(如果你使用_stdcall被调用者清理堆栈的话)会弹出不同的大小。因此,ESP不会返回正确的值。

(当然,这些参数以及它们下面的其他参数在被调用函数中看似乱码,但有时你可以在没有明显崩溃的情况下幸存下来。)

如果您可以访问所有代码,只需重新编译即可。

答案 1 :(得分:18)

答案 2 :(得分:11)

拒绝检查不是正确的解决方案。你必须弄清楚你的召唤惯例搞砸了什么。

有很多方法可以在不明确指定函数的情况下更改函数的调用对话。 extern“C”会这样做,STDMETHODIMP / IFACEMETHODIMP也会这样做,其他宏也可以这样做。

我相信如果在WinDBG(http://www.microsoft.com/whdc/devtools/debugging/default.mspx)下运行你的程序,运行时应该在遇到问题的时候中断。您可以查看调用堆栈并确定哪个函数存在问题,然后查看其定义和调用者使用的声明。

答案 3 :(得分:5)

当代码试图在不符合预期类型的​​对象上调用函数时,我看到了这个错误。

因此,类层次结构:包含子项的父项:Child1和Child2

Child1* pMyChild = 0;
...
pMyChild = pSomeClass->GetTheObj();// This call actually returned a Child2 object
pMyChild->SomeFunction();          // "...value of ESP..." error occurs here

答案 4 :(得分:5)

我在使用VC ++程序调用的AutoIt API时出现了类似的错误。

    typedef long (*AU3_RunFn)(LPCWSTR, LPCWSTR);

但是,当我更改包含WINAPI的声明时,如前面的线程所示,问题就消失了。

没有任何错误的代码如下:

typedef long (WINAPI *AU3_RunFn)(LPCWSTR, LPCWSTR);

AU3_RunFn _AU3_RunFn;
HINSTANCE hInstLibrary = LoadLibrary("AutoItX3.dll");
if (hInstLibrary)
{
  _AU3_RunFn = (AU3_RunFn)GetProcAddress(hInstLibrary, "AU3_WinActivate");
  if (_AU3_RunFn)
     _AU3_RunFn(L"Untitled - Notepad",L"");
  FreeLibrary(hInstLibrary);
}

答案 5 :(得分:2)

我在调用DLL中的函数时遇到此错误,该函数是使用较新版本的VC(2008)的2005版之前的Visual C ++编译的。 该功能有这个签名:

LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );

问题是time_t的大小在2005之前版本中是32位,但是自VS2005以来是64位(定义为_time64_t)。函数的调用需要一个32位变量,但在从VC&gt; = 2005调用时获得64位变量。当使用WINAPI调用约定时,函数的参数通过堆栈传递,这会破坏堆栈并生成上面提到的错误信息(&#34;运行时检查失败#0 ......&#34;)。

要解决此问题,可以

#define _USE_32BIT_TIME_T

在包含DLL的头文件之前或者 - 更好 - 根据VS版本更改头文件中函数的签名(2005之前的版本不知道_time32_t!) :

#if _MSC_VER >= 1400
LONG WINAPI myFunc( _time32_t, SYSTEMTIME*, BOOL* );
#else
LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );
#endif

请注意,您需要在调用程序中使用_time32_t而不是time_t

答案 6 :(得分:2)

值得指出的是,这也可能是Visual Studio的错误。

我在VS2017,Win10 x64上遇到了这个问题。起初它是有道理的,因为我做了奇怪的事情,将其转换为派生类型并将其包装在lambda中。但是,我将代码恢复到先前的提交并仍然得到错误,即使它之前没有。

我尝试重新启动然后重建项目,然后错误就消失了。

答案 7 :(得分:1)

你有任何typedef'd函数原型(例如int(* fn)(int a,int b))

如果你是dom,你可能会把原型弄错了。

ESP是调用函数的一个错误(你能告诉调试器中哪一个吗?)参数不匹配 - 即堆栈已恢复到调用函数时启动的状态。

如果你正在加载需要声明为extern的C ++函数,你也可以得到这个.C-C使用cdecl,C ++默认使用stdcall调用约定(IIRC)。在导入的函数原型周围放置一些extern C包装器,你可以修复它。

如果你可以在调试器中运行它,你会看到函数immediatey。如果没有,您可以设置DrWtsn32来创建一个minidump,您可以将其加载到windbg中以查看错误发生时的callstack(您需要使用符号或mapfile来查看函数名称)。

答案 8 :(得分:1)

esp可能搞砸的另一种情况是无意中缓冲区溢出,通常是错误地使用指针来超越数组的边界。假设您有一些看起来像

的C函数
int a, b[2];

写入b[3]可能会更改a,而过去的任何地方都可能会阻止堆叠中保存的esp

答案 9 :(得分:1)

如果使用除编译之外的调用约定调用该函数,则会出现此错误。

Visual Studio使用默认的调用约定设置,该设置在项目的选项中进行了decalred。检查orignal项目设置和新库中的值是否相同。一个过于雄心勃勃的开发者可能会将其设置为原始的_stdcall / pascal,因为与默认的cdecl相比,它减少了代码大小。因此基本进程将使用此设置,新库将获得导致问题的默认cdecl

由于您已经说过不使用任何特殊的呼叫约定,这似乎是一个很好的概率。

还要对标头执行diff操作,以查看进程看到的声明/文件是否与编译库的声明/文件相同。

ps:警告消失是BAAAD。潜在的错误仍然存​​在。

答案 10 :(得分:1)

您是在创建静态库还是DLL?如果是DLL,如何定义导出;如何创建导入库?

libs 的函数原型是否与定义函数的函数声明相同?

答案 11 :(得分:1)

访问COM对象时,我发生了这种情况(Visual Studio 2010)。我在调用QueryInterface时传递了另一个接口A的GUID,但后来我将检索到的指针转换为接口B.这导致对具有完全签名的一个函数调用,这说明了堆栈(和ESP)是搞砸了。

传递接口B的GUID解决了问题。

答案 12 :(得分:1)

将函数移动到dll并使用LoadLibrary和GetProcAddress动态加载dll后,我遇到了同样的错误。我曾宣布过extern&#34; C&#34;由于装饰,在DLL中的功能。因此,也将调用约定更改为__cdecl。我在加载代码中声明函数指针为__stdcall。一旦我在加载代码中将函数指针从__stdcall更改为_cdecl,运行时错误就消失了。

答案 13 :(得分:1)

在我的MFC C ++应用程序中,遇到与Weird MSC 8.0 error: “The value of ESP was not properly saved across a function call…”中报告的问题相同的问题。该帖子有超过42K的视图和16个答案/评论,没有一个将编译器归咎于问题。至少在我的情况下,我可以证明VS2015编译器有问题。

我的开发和测试设置如下:我有3台PC,所有这些都运行Win10版本10.0.10586。所有都在编译VS2015,但这是不同的。其中两个VS2015具有更新2,而另一个已应用更新3。具有更新3的PC可以工作,但其他两个更新2的操作失败,并且出现与上面发布的报告相同的错误。我的MFC C ++应用程序代码在所有三台PC上完全相同。

结论:至少在我的应用程序中,编译器版本(Update 2)包含一个破坏我的代码的错误。我的应用程序大量使用std :: packaged_task,所以我希望问题出现在相当新的编译器代码中。

答案 14 :(得分:0)

如果您在Windows API中使用任何回调函数,则必须使用CALLBACK和/或WINAPI声明它们。这将应用适当的装饰,使编译器生成正确清理堆栈的代码。例如,在Microsoft的编译器上,它添加了__stdcall

Windows始终使用__stdcall约定,因为它会导致(略微)更小的代码,清除发生在被调用的函数中而不是每个调用站点。但它与varargs函数不兼容(因为只有调用者知道他们推送了多少个参数)。

答案 15 :(得分:0)

ESP是堆栈指针。所以根据编译器,你的堆栈指针搞砸了。如果没有看到某些代码,很难说(或者如果)这种情况会如何发生。

您可以重现此内容的最小代码段是什么?

答案 16 :(得分:0)

这是一个产生该错误的精简C ++程序。使用(Microsoft Visual Studio 2003)编译产生上述错误。

#include "stdafx.h"
char* blah(char *a){
  char p[1];
  strcat(p, a);
  return (char*)p;
}
int main(){
  std::cout << blah("a");
  std::cin.get();
}

错误: “运行时检查失败#0 - ESP的值未在函数调用中正确保存。这通常是调用使用一个调用约定声明的函数,其中函数指针使用不同的调用约定声明。”

答案 17 :(得分:0)

我在这里遇到同样的问题。我正在更新一些调用FARPROC函数指针的非常旧的代码。如果您不知道,FARPROC是具有ZERO类型安全性的功能指针。它是一个typdef'd函数指针的C等价物,没有编译器类型检查。 例如,假设你有一个带3个参数的函数。你将FARPROC指向它,然后用4个参数而不是3来调用它。额外的参数将额外的垃圾推入堆栈,当它弹出时,ESP现在与启动时不同。所以我通过删除FARPROC函数调用的调用的额外参数来解决它。

答案 18 :(得分:0)

不是最好的答案,但我只是从头开始重新编译代码(在VS中重建)然后问题就消失了。