3

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.

Community
  • 1
  • 1
Petwoip
  • 1,365
  • 2
  • 17
  • 25
  • 2
    Of course your code is needed. But remember to give a minimal example. – fefe Dec 30 '11 at 06:44
  • 1
    watch out for the hidden argument (`this` pointer) in non-static method call – LeleDumbo Dec 30 '11 at 07:04
  • Compiler and platform, please. – lapk Dec 30 '11 at 07:14
  • Thanks guys - I've updated my post. @LeleDumbo: How should I use "this" with function pointers? – Petwoip Dec 30 '11 at 08:13
  • The code where you call the `receiverFunction`. Best provide a small snippet that exhibits the problem and is *complete*, i.e., can be copy-pasted for testing. – Xeo Dec 30 '11 at 08:25
  • I don't call the receiverFunction method anywhere at the moment. I left the addEventListener method empty. I'll add the call code when the other stuff works. – Petwoip Dec 30 '11 at 08:28
  • Then what function call is the runtime issuing the error about? – Michael Burr Dec 30 '11 at 08:30
  • Then your code looks [something like this](http://ideone.com/haP7m)? – Xeo Dec 30 '11 at 08:34
  • I whipped up an example project and updated all of the code in my question. Same problem as before. – Petwoip Dec 30 '11 at 08:48
  • I get no error when running the provided code. :/ (I *did* put it all in one cpp file, but theoretically that shouldn't matter.) Maybe something is borked with your Visual Studio installation? – Xeo Dec 30 '11 at 09:07
  • Could you send me the code you used? Also, are you running in Windows VS2010? – Petwoip Dec 30 '11 at 09:08
  • I just tried my code in Code::Blocks (which uses MinGW) and my code ran fine. What about Visual Studio could be causing this problem? – Petwoip Dec 30 '11 at 09:20
  • Oh my god, got the same error with the OP in VS 2010 ... – fefe Dec 30 '11 at 09:41

3 Answers3

9

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)();
Marvin
  • 482
  • 5
  • 19
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • https://social.msdn.microsoft.com/Forums/en-US/3c065ad7-a6cc-460a-8114-25b5ee01c76f/stack-corruption-when-passing-pointer-to-member-function-of-a-forward-referenced-class seems to be the currently working link of the first URL. – Marvin Sep 13 '19 at 14:38
  • The Connect web site is discontinued with no Wayback Machine backup of their articles. Doesn't matter, focus on the solution. – Hans Passant Sep 13 '19 at 14:40
4

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.

fefe
  • 3,342
  • 2
  • 23
  • 45
  • Ahh, this actually sounds plausible. Depending on the inheritance and virtual-ness of any functions, the type of member-function-pointer is actually more or less complicated. You can read about it in the source code of [this delegate implementation](http://www.codeproject.com/KB/cpp/FastDelegate.aspx). It's arcane. – Xeo Dec 30 '11 at 10:17
  • @Xeo: I've heard of this. I also wonder whether the code is well formed according to the specification. – fefe Dec 30 '11 at 10:37
  • @fefe: This still seems to be a bug, because VS should stick to one member-function-pointer type. – Xeo Dec 30 '11 at 10:41
  • VS has options for selecting best or worst case member function pointer sizes for incomplete types. The size varies a lot depending on potential virtual inheritance and such things. – Bo Persson Dec 30 '11 at 10:54
  • @SimonRichter: I used the default, seems to be /W3. Will add the command line. – fefe Dec 30 '11 at 10:55
2

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!

Petwoip
  • 1,365
  • 2
  • 17
  • 25