Member function pointer runtime error - The value of ESP was not properly saved across a function call

。_饼干妹妹 提交于 2019-12-06 11:18:47

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!