最近、Visual Studioプロジェクトの一部をライブラリに分割しようとしましたが、ライブラリプロジェクトの1つを依存関係として使用するテストプロジェクトでは、すべてがコンパイルおよびビルドできるように見えました。ただし、アプリケーションを実行しようとすると、次の厄介な実行時エラーメッセージが表示されました。
実行時チェックの失敗#0-ESPの値は関数呼び出しで正しく保存されませんでした。これは通常、異なる呼び出し規約で宣言された関数ポインターを呼び出した結果です。
関数の呼び出し規則(__cdeclなど)を指定することすらしていないため、すべてのコンパイラスイッチはデフォルトのままです。私はチェックしました、そして、プロジェクト設定はライブラリとテストプロジェクト全体で呼び出し規約に関して一貫しています。
更新:開発者の1人が、「Basic Runtime Checks」プロジェクト設定を「Both(/ RTC1、equiv。to/RTCsu)」から「Default」に変更し、ランタイムが消失したため、プログラムは明らかに正しく実行されたままになりました。私はこれをまったく信用していません。これは適切な解決策でしたか、それとも危険なハッキングでしたか?
このデバッグエラーは、関数呼び出し後にスタックポインターレジスタが元の値に戻らないこと、つまり、関数呼び出し前のpushesの数が呼び出しの後、同じ数のpopsが続かない。
これには2つの理由があります(動的にロードされるライブラリの両方)。 #1はVC++がエラーメッセージで説明していることですが、これがエラーの最も頻繁な原因だとは思いません(#2を参照)。
1)呼び出し規約の不一致:
発信者と着信者は、だれが何をするかについて適切な合意を持っていません。たとえば、_stdcall
であるDLL関数を呼び出しているが、何らかの理由で_cdecl
(VC++のデフォルト)として宣言されている場合]これは、異なるモジュールなどで異なる言語を使用している場合に頻繁に発生します。
問題のある関数の宣言を調べ、それが2回宣言されていないことを確認する必要があります。
2)不一致のタイプ:
呼び出し元と呼び出し先は同じ型でコンパイルされていません。たとえば、共通ヘッダーはAPIのタイプを定義し、最近変更されました。1つのモジュールは再コンパイルされましたが、他のモジュールは再コンパイルされませんでした。一部のタイプは、呼び出し元と呼び出し先でサイズが異なる場合があります。
その場合、呼び出し元は1つのサイズの引数をプッシュしますが、呼び出し先(呼び出し先がスタックをクリーニングする_stdcall
を使用している場合)は異なるサイズをポップします。 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.
いくつかの調査の後、行の1つを次のように変更しました。
typedef bool (WINAPI*tSetSuspendStateSig)(BOOL, BOOL, BOOL);
問題を解決しました。 SetSuspendStateが見つかったヘッダーファイル(SDKの一部であるpowrprof.h)を見ると、関数プロトタイプが次のように定義されていることがわかります。
BOOLEAN WINAPI SetSuspendState(BOOLEAN, BOOLEAN, BOOLEAN);
皆さんも同様の問題を抱えています。 .dllから特定の関数を呼び出す場合、そのシグネチャはおそらくオフになっています。 (私の場合は、WINAPIキーワードがありませんでした)。
それが将来の人々に役立つことを願っています! :-)
乾杯。
チェックを黙らせることは正しい解決策ではありません。あなたはあなたの呼び出し規約で台無しにされているものを把握する必要があります。
明示的に指定せずに関数の呼び出し元を変更する方法はかなりあります。 extern "C"がそれを行い、STDMETHODIMP/IFACEMETHODIMPもそれを行います。他のマクロも同様に行います。
WinDBG( http://www.Microsoft.com/whdc/devtools/debugging/default.mspx )でプログラムを実行すると、その問題が発生した時点でランタイムが中断するはずです。呼び出しスタックを調べて、どの関数に問題があるかを特定し、その定義と呼び出し元が使用する宣言を調べることができます。
コードが予期されたタイプではないオブジェクトの関数を呼び出そうとしたときに、このエラーが表示されました。
したがって、クラス階層:子を持つ親: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);
}
これはVisual Studioのバグでもある可能性があることを指摘する価値があります。
VS2017、Win10 x64でこの問題が発生しました。私はこれを派生型にキャストし、ラムダでラップする奇妙なことをしていたので、最初は意味がありました。しかし、以前はコミットしていなかったにもかかわらず、コードを以前のコミットに戻し、それでもエラーが発生しました。
プロジェクトを再起動してから再構築しようとすると、エラーはなくなりました。
新しいバージョンのDLL(2008)から2005年以前のバージョンのVisual C++でコンパイルされたVCこの関数には次のシグネチャがありました。
LONG WINAPI myFunc( time_t, SYSTEMTIME*, BOOL* );
問題は、time_t
のサイズが2005年以前のバージョンでは32ビットであったが、VS2005以降は64ビットである(_time64_t
として定義されている)ことでした。関数の呼び出しは32ビット変数を想定していますが、VC> = 2005から呼び出された場合は64ビット変数を取得します。関数のパラメーターは、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
を使用する必要があることに注意してください。
esp
がめちゃくちゃになるもう1つのケースは、通常は誤ってポインターを使用して配列の境界を越えて作業することによる、意図しないバッファーオーバーフローです。次のようなC関数があるとします
int a, b[2];
b[3]
への書き込みはおそらくa
を変更し、それ以降はスタック上の保存されたesp
を使い果たしそうです。
MFC C++アプリでは、 Weird MSC 8.0 error:“ ESPの値が正しく保存されませんでした。関数呼び出しを渡って…” 。投稿には42Kを超えるビューがあり、16の回答/コメントがありますが、いずれもコンパイラーを問題として非難しません。少なくとも私の場合、VS2015コンパイラーに障害があることを示します。
開発とテストのセットアップは次のとおりです。3台のPCでWin10バージョン10.0.10586を実行しています。すべてがVS2015でコンパイルされていますが、ここに違いがあります。 VS2015の2つにはUpdate 2があり、もう1つにはUpdate 3が適用されています。 Update 3を搭載したPCは動作しますが、Update 2を搭載した他の2台は、上記の投稿で報告されたのと同じエラーで失敗します。私のMFC C++アプリコードは、3台すべてのPCでまったく同じです。
結論:少なくとも私のアプリの場合、コンパイラバージョン(Update 2)にはコードを壊すバグが含まれていました。私のアプリはstd :: packaged_taskを多用しているので、問題はかなり新しいコンパイラコードにあったと予想しています。
静的ライブラリまたはDLLを作成していますか? DLLの場合、エクスポートはどのように定義されますか。インポートライブラリはどのように作成されますか?
ライブラリ内の関数のプロトタイプexactlyは、関数が定義されている関数宣言と同じですか?
これは、COMオブジェクト(Visual Studio 2010)にアクセスしたときに起こりました。 QueryInterfaceの呼び出しで別のインターフェイスAのGUIDを渡しましたが、取得したポインターをインターフェイスBとしてキャストしました。スタック(およびESP)が台無しになっていることを説明します。
インターフェイスBのGUIDを渡すと、問題が修正されました。
typedefされた関数のプロトタイプはありますか(例:int(* fn)(int a、int b))
domを使用すると、プロトタイプが間違っている可能性があります。
ESPは、パラメーターの不一致がある関数の呼び出しに関するエラーです(デバッガーでどれがわかりますか?)。つまり、スタックは、関数を呼び出したときに開始した状態に戻りました。
Extern C-Cはcdeclを使用し、C++はデフォルトでstdcall呼び出し規約(IIRC)を使用して宣言する必要があるC++関数をロードしている場合にも取得できます。インポートされた関数プロトタイプの周りにいくつかの外部Cラッパーを配置すると、修正できます。
デバッガで実行できる場合は、関数がすぐに表示されます。そうでない場合、DrWtsn32を設定して、エラー発生時にコールスタックを表示するためにwindbgにロードできるミニダンプを作成できます(ただし、関数名を表示するにはシンボルまたはマップファイルが必要です)。
関数をdllに移動し、LoadLibraryとGetProcAddressを使用してdllを動的にロードした後、まったく同じエラーが発生していました。装飾のため、dllの関数に対してextern "C"を宣言しました。そのため、呼び出し規約も__cdeclに変更されました。ロード中のコードで関数ポインタを__stdcallとして宣言していました。ロードコードで関数ポインターを__stdcallから__cdeclに変更すると、ランタイムエラーはなくなりました。
関数がコンパイルされたもの以外の呼び出し規約で呼び出された場合、このエラーが発生します。
Visual Studioは、プロジェクトのオプションで宣言されているデフォルトの呼び出し規約設定を使用します。元のプロジェクト設定と新しいライブラリでこの値が同じかどうかを確認します。意欲的な開発者は、デフォルトのcdeclに比べてコードサイズを小さくするため、オリジナルの_stdcall/Pascalに設定できます。そのため、基本プロセスはこの設定を使用し、新しいライブラリはデフォルトのcdeclを取得するため、問題が発生します
あなたは特別な呼び出し規約を使用しないと言ったので、これは良い確率のようです。
また、ヘッダーでdiffを実行して、プロセスで表示される宣言/ファイルが、ライブラリがコンパイルされるものと同じであるかどうかを確認します。
ps:警告を消すことはBAAADです。根底にあるエラーは引き続き存在します。
Windows APIでコールバック関数を使用している場合、CALLBACK
やWINAPI
を使用して宣言する必要があります。これにより、適切な装飾が適用され、スタックを正しくクリーニングするコードがコンパイラーに生成されます。たとえば、Microsoftのコンパイラでは、__stdcall
。
Windowsは常に__stdcall
規約により、コードが(わずかに)小さくなり、すべての呼び出しサイトではなく、呼び出された関数でクリーンアップが行われます。ただし、varargs関数とは互換性がありません(呼び出し元だけがプッシュした引数の数を知っているため)。
ESPはスタックポインターです。そのため、コンパイラによると、スタックポインタが台無しになっています。なんらかのコードを見ずにこれがどのように(または発生するか)言うのは困難です。
これを再現するために取得できる最小のコードセグメントは何ですか?
そのエラーを生成する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の値は関数呼び出しで適切に保存されませんでした。これは通常、1つの呼び出し規約で宣言された関数を呼び出した結果です。異なる呼び出し規約で宣言されたポインター。」
最良の答えではありませんが、コードを最初から再コンパイル(VSで再構築)しただけで、問題はなくなりました。
私は職場で同じ問題を抱えていました。 FARPROC関数ポインターを呼び出していた非常に古いコードを更新していました。わからない場合、FARPPROCはZERO型の安全性を持つ関数ポインターです。これは、コンパイラの型チェックを行わない、typdefされた関数ポインタに相当するCです。たとえば、3つのパラメーターを取る関数があるとします。 FARPROCをポイントし、3の代わりに4つのパラメーターで呼び出します。追加のパラメーターは、余分なゴミをスタックにプッシュし、ポップすると、ESPはそれとは異なりますだから私はFARPROC関数呼び出しの呼び出しに余分なパラメーターを削除して解決しました。