52

最近、一部のVisual Studioプロジェクトをライブラリに分割しようとしましたが、ライブラリプロジェクトの1つを依存関係として持つテストプロジェクトでは、すべてが正常にコンパイルおよびビルドされたようです。ただし、アプリケーションを実行しようとすると、次の厄介な実行時エラーメッセージが表示されました。

実行時チェックの失敗#0-ESPの値は、関数呼び出し全体で適切に保存されませんでした。これは通常、異なる呼び出し規約で宣言された関数ポインターを呼び出した結果です。

関数の呼び出し規約(__cdeclなど)を指定したことはなく、すべてのコンパイラスイッチをデフォルトのままにします。私がチェックしたところ、プロジェクト設定は、ライブラリとテストプロジェクト全体で規約を呼び出すために一貫しています。

更新:開発者の1人が、「BasicRuntimeChecks」プロジェクト設定を「Both(/ RTC1、equiv。to / RTCsu)」から「Default」に変更し、ランタイムが消えて、プログラムが明らかに正しく実行されたままになりました。私はこれをまったく信用していません。これは適切な解決策でしたか、それとも危険なハックでしたか?

4

19 に答える 19

53

このデバッグ エラーは、スタック ポインタ レジスタが関数呼び出し後に元の値に戻されていないことを意味します。つまり、関数呼び出し前のプッシュ回数の後に、呼び出し後の同じ回数のポップが続きませんでした。

私が知っているこれには2つの理由があります(両方とも動的にロードされたライブラリを使用しています)。#1 は VC++ がエラー メッセージで説明しているものですが、これがエラーの最も頻繁な原因ではないと思います (#2 を参照)。

1) 呼び出し規約の不一致:

呼び出し元と呼び出し先は、誰が​​何をするかについて適切な合意を持っていません。たとえば、 である DLL 関数を呼び出しているが_stdcall、何らかの理由で_cdecl呼び出しで (VC++ のデフォルト) として宣言している場合。これは、異なるモジュールで異なる言語を使用している場合などによく発生します。

問題のある関数の宣言を検査し、それが 2 回宣言されていないこと、および異なる方法で宣言されていないことを確認する必要があります。

2) 型の不一致:

呼び出し元と呼び出し先が同じ型でコンパイルされていません。たとえば、API で型を定義する共通ヘッダーが最近変更され、一方のモジュールは再コンパイルされましたが、もう一方は再コンパイルされませんでした。つまり、一部の型は、呼び出し元と呼び出し先でサイズが異なる場合があります。

その場合、呼び出し元は 1 つのサイズの引数をプッシュしますが、呼び出し先 (呼び出し_stdcall先がスタックを消去する場所を使用している場合) は異なるサイズをポップします。したがって、ESP は正しい値に戻されません。

(もちろん、これらの引数とその下にある他の引数は、呼び出された関数では文字化けしているように見えますが、目に見えるクラッシュなしで生き残ることができる場合もあります。)

すべてのコードにアクセスできる場合は、単純に再コンパイルしてください。

于 2009-08-26T07:15:13.707 に答える
20

私は他のフォーラムでこれを読みました

私は同じ問題を抱えていましたが、修正しました。次のコードから同じエラーが発生しました。

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 キーワードの欠落でした)。

それが将来の人々に役立つことを願っています!:-)

乾杯。

于 2010-07-26T08:07:12.963 に答える
11

チェックを黙らせることは正しい解決策ではありません。呼び出し規約で何が台無しになっているのかを理解する必要があります。

明示的に指定せずに関数の呼び出しを変更する方法はたくさんあります。extern "C"がそれを行い、STDMETHODIMP / IFACEMETHODIMPもそれを行い、他のマクロも同様に行う可能性があります。

WinDBG(http://www.microsoft.com/whdc/devtools/debugging/default.mspx)でプログラムを実行すると、問題が発生した時点でランタイムが中断するはずです。呼び出しスタックを調べて、問題のある関数を特定してから、その定義と呼び出し元が使用する宣言を調べることができます。

于 2008-09-27T00:53:57.737 に答える
6

コードが予期されたタイプではないオブジェクトで関数を呼び出そうとしたときに、このエラーが発生しました。

したがって、クラス階層:子を持つ親:Child1とChild2

Child1* pMyChild = 0;
...
pMyChild = pSomeClass->GetTheObj();// This call actually returned a Child2 object
pMyChild->SomeFunction();          // "...value of ESP..." error occurs here
于 2009-05-14T14:53:09.767 に答える
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);
}
于 2012-06-17T09:00:34.850 に答える
3

新しいバージョンの VC (2008) から 2005 年より前のバージョンの Visual C++ でコンパイルされた DLL で関数を呼び出すと、このエラーが発生しました。関数には次の署名がありました。

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

問題は、time_tのサイズが 2005 年より前のバージョンでは 32 ビットであるのに対し、VS2005 ( として定義されている_time64_t) 以降は 64 ビットであるということでした。関数の呼び出しは 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

于 2014-05-21T06:41:50.507 に答える
2

関数をdllに移動し、LoadLibraryとGetProcAddressを使用してdllを動的にロードした後、まったく同じエラーが発生しました。装飾のため、dll 内の関数に extern "C" を宣言しました。そのため、呼び出し規約も __cdecl に変更されました。読み込みコードで関数ポインタを __stdcall と宣言していました。読み込みコードで関数ポインタを __stdcall から __cdecl に変更すると、ランタイム エラーはなくなりました。

于 2015-01-10T19:35:08.887 に答える
1

静的ライブラリまたはDLLを作成していますか?DLLの場合、エクスポートはどのように定義されますか。インポートライブラリはどのように作成されますか?

libs内の関数のプロトタイプは、関数が定義されている関数宣言とまったく同じですか?

于 2008-09-27T00:55:49.900 に答える
1

typedefされた関数プロトタイプはありますか(例:int(* fn)(int a、int b))

もしあなたがdomなら、あなたはプロトタイプを間違えたかもしれません。

ESPは、パラメーターの不一致がある関数の呼び出し時のエラーです(デバッガーでどれを判別できますか?)。つまり、スタックは、関数を呼び出したときに開始した状態に復元されます。

externCとして宣言する必要があるC++関数をロードしている場合にも、これを取得できます。Cはcdeclを使用し、C ++はデフォルトでstdcall呼び出し規約(IIRC)を使用します。インポートされた関数プロトタイプの周りにいくつかのexternCラッパーを配置すると、修正できます。

デバッガーで実行できる場合は、関数がすぐに表示されます。そうでない場合は、DrWtsn32を設定して、エラー時にコールスタックを表示するためにwindbgにロードできるミニダンプを作成できます(ただし、関数名を表示するには、シンボルまたはマップファイルが必要です)。

于 2008-09-27T01:02:43.753 に答える
1

関数がコンパイルされたものとは異なる呼び出し規則で呼び出された場合、このエラーが発生します。

Visual Studio は、プロジェクトのオプションで宣言された既定の呼び出し規約設定を使用します。この値が元のプロジェクト設定と新しいライブラリで同じかどうかを確認します。野心的な開発者は、デフォルトの cdecl と比較してコード サイズを削減するため、オリジナルでこれを _stdcall/pascal に設定できた可能性があります。したがって、基本プロセスはこの設定を使用し、新しいライブラリはデフォルトの cdecl を取得するため、問題が発生します

特別な呼び出し規約を使用していないとおっしゃっていたので、これはかなりの確率であると思われます。

また、プロセスが参照する宣言/ファイルが、ライブラリがコンパイルされたものと同じかどうかを確認するために、ヘッダーの diff を実行します。

ps : 警告を消すのは BAAAD です。根本的なエラーは引き続き発生します。

于 2008-09-28T18:13:21.770 に答える
1

espめちゃくちゃになる可能性のある別のケースは、通常、ポインターを誤って使用して配列の境界を越えて動作することによる、不注意によるバッファー オーバーフローです。次のような C 関数があるとします。

int a, b[2];

への書き込みb[3]はおそらく変更され、それ以降はスタックaに保存されている可能性があります。esp

于 2008-09-27T03:32:34.000 に答える
0

ESPはスタックポインタです。したがって、コンパイラによると、スタックポインタが台無しになっています。コードを見ずにこれがどのように発生するか(または発生するかどうか)を判断するのは困難です。

これを再現するために取得できる最小のコードセグメントは何ですか?

于 2008-09-27T00:53:12.387 に答える
0

Windows API でコールバック関数を使用している場合は、CALLBACKand/orを使用して宣言する必要がありますWINAPI。これにより、適切な装飾が適用され、コンパイラがスタックを正しくクリーンアップするコードを生成します。たとえば、Microsoft のコンパイラでは、__stdcall.

Windows では、コードが (わずかに) 小さくなるため、常に__stdcall規則を使用してきました。すべての呼び出しサイトではなく、呼び出された関数でクリーンアップが行われます。ただし、varargs 関数とは互換性がありません (プッシュした引数の数を知っているのは呼び出し元だけだからです)。

于 2008-09-27T10:40:41.337 に答える
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 の値が関数呼び出し全体で適切に保存されませんでした。これは通常、ある呼び出し規則で宣言された関数を、別の呼び出し規則で宣言された関数ポインターで呼び出した結果です。」

于 2012-05-24T21:16:25.000 に答える