怪异的MSC 8.0错误:“ESP的值没有正确保存在一个函数调用…”

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

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

我们甚至从来没有为我们的函数指定调用约定(__cdecl等),使所有编译器开关保持默认状态。 我检查了一下,项目设置对于在库和testing项目中调用约定是一致的。

更新:我们的一个开发人员将“基本运行时间检查”项目设置从“Both(/ RTC1,equiv。to / RTCsu)”更改为“Default”,运行时间消失,程序运行显然正常。 我完全不信任这个。 这是一个合适的解决scheme,还是一个危险的黑客?

这个debugging错误意味着堆栈指针寄存器在函数调用后没有返回到它原来的值,也就是说,在函数调用之前推动的次数没有跟在调用之后的相同次数的popup

有两个原因,我知道(都与dynamic加载库)。 #1是VC ++在错误信息中描述的,但我不认为这是错误的最常见原因(见#2)。

1)不匹配的调用约定:

主叫方和被叫方之间对于谁将要做什么没有一个合适的协议。 例如,如果您调用的是_stdcall的DLL函数,但是由于某种原因,您在调用时将其声明为_cdecl (默认为VC ++)。 如果你在不同的模块中使用不同的语言,这将会发生很多

您将不得不检查有问题的函数的声明,并确保它没有被声明两次,并且不同。

2)不匹配的types:

调用者和被调用者不会使用相同的types进行编译。 例如,一个普通的头文件定义了API中的types,并且最近已经改变了,一个模块被重新编译了,但另一个模块却不是 – 也就是说某些types在调用者和被调用者中可能有不同的大小。

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

(当然,这些论点和其他的论点,在被调用的函数中会显得乱七八糟,但是有时你可以在没有可见的崩溃的情况下生存下来)。

如果你有权访问所有的代码,只需重新编译它。

我在其他论坛读到这个

我有同样的问题,但我只是固定它。 我得到了下面的代码相同的错误:

 HMODULE hPowerFunctions = LoadLibrary("Powrprof.dll"); typedef bool (*tSetSuspendStateSig)(BOOL, BOOL, BOOL); tSetSuspendState SetSuspendState = (tSuspendStateSig)GetProcAddress(hPowerfunctions, "SetSuspendState"); result = SetSuspendState(false, false, false); <---- This line was where the error popped up. 

经过一番调查,我改变了其中的一条:

 typedef bool (WINAPI*tSetSuspendStateSig)(BOOL, BOOL, BOOL); 

解决了这个问题。 如果你看一下发现SetSuspendState的头文件(powrprof.h,SDK的一部分),你会看到函数原型被定义为:

 BOOLEAN WINAPI SetSuspendState(BOOLEAN, BOOLEAN, BOOLEAN); 

所以你们有类似的问题。 当您从.dll调用给定的函数时,其签名可能是closures的。 (在我的情况下,这是失踪的WINAPI关键字)。

希望能帮助未来的人! 🙂

干杯。

沉默支票是不正确的解决scheme。 你必须弄清楚什么是你的调用惯例搞砸了。

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

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

当代码试图调用一个不是预期types的​​对象的函数时,我看到了这个错误。

所以,类层次结构:带有子项的父项:Child1和Child2

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

我从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); } 

我得到这个错误调用一个DLL中的function,它是从一个新的版本的VC(2008年)与2005年以前的版本的Visual C ++编译的。 该函数有这个签名:

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

问题是,在2005年以前的版本中, time_t的大小是32位,但是从VS2005开始64位(被定义为_time64_t )。 函数调用需要一个32位的variables,但从VC> = 2005调用时会得到一个64位的variables。当使用WINAPI调用约定时,函数的参数通过堆栈传递,这会破坏堆栈并生成上述错误消息(“运行检查失败#0 …”)。

为了解决这个问题,有可能

 #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

你正在创build静态库或DLL? 如果是DLL,那么如何定义输出; 如何创build导入库?

libs中函数的原型是否与定义函数的函数声明完全相同

你有任何types定义的函数原型(例如int(* fn)(int a,int b))

如果你愿意的话,你可能会弄错原型。

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

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

如果你可以在debugging器中运行,你会看到函数quicky。 如果不是的话,你可以设置DrWtsn32创build一个小型转储,你可以加载到windbg来查看错误发生时的调用堆栈(你需要使用符号或mapfile来查看函数名称)。

另一个esp可能会搞砸的例子是无意的缓冲区溢出,通常是通过错误地使用指针来超越数组边界。 假设你有一些C函数,看起来像

 int a, b[2]; 

写给b[3]可能会改变a ,以及过去的任何地方,可能会将已保存的esp在堆栈上。

如果函数被调用的时候调用的是一个非编译的调用约定,那么你会得到这个错误。

Visual Studio使用在项目选项中被剥离的默认调用约定设置。 检查orignal项目设置和新库中的值是否相同。 一个雄心勃勃的开发者可以将它设置为原始的_stdcall / pascal,因为它比默认的cdecl减less了代码的大小。 所以基本的过程将使用这个设置,新的库得到导致问题的默认的cdecl

既然你说过你没有使用任何特殊的调用约定,这似乎是一个很好的概率。

还要对标题进行比较,以查看stream程看到的声明/文件是否与库编译的声明/文件相同。

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

访问COM对象时发生了这种情况(Visual Studio 2010)。 我将另一个接口A的GUID传递给了QueryInterface,但是之后我将检索到的指针作为接口B进行了转换。这导致了对一个完全签名的函数调用,这个函数调用了堆栈(和ESP)弄乱。

通过接口B的GUID解决了这个问题。

将函数移动到dll并dynamic加载DLL和LoadLibrary和GetProcAddress后,我有这个完全相同的错误。 由于装饰,我已经在dll中声明了extern“C”。 所以这也改变了调用约定到__cdecl。 我在加载代码中声明函数指针是__stdcall。 一旦我在加载代码中将函数指针从__stdcall更改为_cdecl,运行时错误就消失了。

ESP是堆栈指针。 所以根据编译器,你的堆栈指针正在变得混乱。 如果没有看到一些代码,很难说如何(或如果)发生这种情况。

什么是最小的代码段可以得到重现呢?

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

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

这是一个精简的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的值在函数调用中没有正确保存,这通常是调用一个调用约定的函数声明的结果,而函数指针的调用约定是不同的。

我在工作中遇到了同样的问题。 我正在更新一些非常旧的代码,这是调用FARPROC函数指针。 如果你不知道,FARPROC是ZEROtypes安全的函数指针。 这是一个typdef'd函数指针的C等价物,没有编译器types检查。 所以举个例子,假设你有一个带3个参数的函数。 你把一个FARPROC指向它,然后用4个参数而不是3来调用它。额外的参数把额外的垃圾推到堆栈上,当它popup时,ESP现在和启动时不同。 所以我通过除去调用FARPROC函数调用的额外参数来解决这个问题。

在我的MFC的C + +应用程序中遇到奇怪的MSC 8.0错误中报告相同的问题:“ESP的价值没有正确保存跨函数调用…” 。 该发布有超过42K的意见和16个答复/评论没有任何指责编译器的问题。 至less在我的情况下,我可以certificateVS2015编译器有问题。

我的开发和testing设置如下:我有3台电脑运行Win10 10.0.10586版本。 所有的编译与VS2015,但这里是区别。 两个VS2015有更新2,而另一个有更新3。 具有更新3的PC可以正常工作,但其他两个更新2则会失败,并显示与上面报告中相同的错误。 我的MFC C ++应用程序代码在所有三台PC上完全一样。

结论:至less在我的情况下,我的应用程序的编译器版本(更新2)包含了一个错误,打破了我的代码。 我的应用程序大量使用std :: packaged_task,所以我期望问题是在相当新的编译器代码。

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