问题
I've been searching for answers to this problem for the past hour but can't find a solution that works. I'm trying to use function pointers to call a non-static member function of a specific object. My code compiles fine, but during runtime I get a nasty runtime exception that says:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
A lot of websites said to specify the calling convention in the method header, so I added __cdecl before it. However, my code encountered the same runtime exception after the change (I tried using other calling conventions as well). I'm not sure why I have to specify cdecl in the first place because my project settings are set to cdecl. I am using some external libraries, but those were working fine before I added this function pointer stuff.
I'm following this: https://stackoverflow.com/a/151449
My code:
A.h
#pragma once
class B;
typedef void (B::*ReceiverFunction)();
class A
{
public:
A();
~A();
void addEventListener(ReceiverFunction receiverFunction);
};
A.cpp
#include "A.h"
A::A(){}
A::~A(){}
void A::addEventListener(ReceiverFunction receiverFunction)
{
//Do nothing
}
B.h
#pragma once
#include <iostream>
#include "A.h"
class B
{
public:
B();
~B();
void testFunction();
void setA(A* a);
void addEvent();
private:
A* a;
};
B.cpp
#include "B.h"
B::B(){}
B::~B(){}
void B::setA(A* a)
{
this->a = a;
}
void B::addEvent()
{
a->addEventListener(&B::testFunction); //This is the offending line for the runtime exception
}
void B::testFunction()
{
//Nothing here
}
main.cpp
#include "A.h"
#include "B.h"
int main()
{
A* a = new A();
B* b = new B();
b->setA(a);
b->addEvent();
}
I'm running with Visual Studio 2010, but I'd like my code to work on other platforms with minimal changes.
回答1:
This is a known problem, necessary ingredients are a member pointer declaration using an incomplete class and having it used in different translation units. An optimization in the MSVC compiler, it uses different internal representations for a member pointers depending on the inheritance.
The workaround is to compile with /vmg or to declare the inheritance explicitly:
class __single_inheritance B;
typedef void (B::*ReceiverFunction)();
回答2:
Seems not many has reproduced the problem, I'll first show the behavior of VS2010 on this piece of code here. (DEBUG build, 32bit OS)
The problem is in B::addEven() and A::addEventListener(). To give me a reference point to check the ESP value, two additional statements are added to B::addEven().
// in B.cpp, where B is complete
void B::addEvent()
{
00411580 push ebp
00411581 mov ebp,esp
00411583 sub esp,0D8h
00411589 push ebx
0041158A push esi
0041158B push edi
0041158C push ecx
0041158D lea edi,[ebp-0D8h]
00411593 mov ecx,36h
00411598 mov eax,0CCCCCCCCh
0041159D rep stos dword ptr es:[edi]
0041159F pop ecx
004115A0 mov dword ptr [ebp-8],ecx
int i = sizeof(ReceiverFunction); // added, sizeof(ReceiverFunction) is 4
004115A3 mov dword ptr [i],4
a->addEventListener(&B::testFunction); //This is the offending line for the runtime exception
004115AA push offset B::testFunction (411041h)
004115AF mov eax,dword ptr [this]
004115B2 mov ecx,dword ptr [eax]
004115B4 call A::addEventListener (4111D6h)
i = 5; // added
004115B9 mov dword ptr [i],5
}
004115C0 pop edi
004115C1 pop esi
004115C2 pop ebx
004115C3 add esp,0D8h
004115C9 cmp ebp,esp
004115CB call @ILT+330(__RTC_CheckEsp) (41114Fh)
004115D0 mov esp,ebp
004115D2 pop ebp
004115D3 ret
// In A.cpp, where B is not complete
void A::addEventListener(ReceiverFunction receiverFunction)
{
00411470 push ebp
00411471 mov ebp,esp
00411473 sub esp,0D8h
00411479 push ebx
0041147A push esi
0041147B push edi
0041147C push ecx
0041147D lea edi,[ebp-0D8h]
00411483 mov ecx,36h
00411488 mov eax,0CCCCCCCCh
0041148D rep stos dword ptr es:[edi]
0041148F pop ecx
00411490 mov dword ptr [ebp-8],ecx
int i = sizeof(receiverFunction); // added, sizeof(receiverFunction) is 10h
00411493 mov dword ptr [i],10h
//Do nothing
}
0041149A pop edi
0041149B pop esi
0041149C pop ebx
0041149D mov esp,ebp
0041149F pop ebp
004114A0 ret 10h
A:: addEventListener() used ret 10h to clear the stack, but only 4 bytes are pushed into the stack (push offset B::testFunction), which cause the stack frame to be corrupted.
Seem that depending whether B is complete or not, sizeof(void B::*func()) would change in VS2010. In OP's code, in A.cpp B is not complete, and the size is 10h. In call site B.cpp, when B is already complete, the size becomes 04h. (This can be checked by sizeof(ReceiverFunction) as shown in the above code). This caused that in the call site, and in the actual code of A::addEventListener(), the size of the augment/parameter are not the same, thus caused stack corruption.
I changed the order of inclusion to make sure B is complete in every translation unit, and the runtime error disappears.
This should be a VS2010 bug ...
Compiler Command Line:
/ZI /nologo /W3 /WX- /Od /Oy- /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Debug\test.pch" /Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue
Linker Command Line:
/OUT:"...\test.exe" /INCREMENTAL /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"Debug\test.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"...\test.pdb" /SUBSYSTEM:CONSOLE /PGD:"...\test.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE
I hid some pathes in the command line.
回答3:
Using /vmg as a compiler option fixed the problem.
However, I decided to use a delegate library instead (http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx), and it works well!
来源:https://stackoverflow.com/questions/8676879/member-function-pointer-runtime-error-the-value-of-esp-was-not-properly-saved