问题
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