过去一个小时,我一直在寻找这个问题的答案,但找不到可行的解决方案。我正在尝试使用函数指针来调用特定对象的非静态成员函数。我的代码可以很好地编译,但是在运行时我得到了一个讨厌的运行时异常,它表示:
运行时检查失败#0-在整个函数调用中ESP的值未正确保存。这通常是由于用一种调用约定声明的函数和用另一种调用约定声明的函数指针进行调用的结果。
许多网站说要在方法标题中指定调用约定,因此我在其前面添加了__cdecl
。但是,更改后,我的代码遇到了相同的运行时异常(我也尝试使用其他调用约定)。我不确定为什么必须首先指定cdecl,因为我的项目设置设置为cdecl。我正在使用一些外部库,但是在添加此函数指针之前,这些库工作正常。
我正在关注:https://stackoverflow.com/a/151449
我的代码:
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();
}
我正在与Visual Studio 2010一起运行,但是我希望我的代码能够在其他平台上以最少的更改来工作。
这是known problem,必要的成分是使用不完整类的成员指针声明,并在不同的翻译单元中使用它。 MSVC编译器中的一项优化,它根据继承对成员指针使用不同的内部表示形式。
解决方法是使用/vmg
或declare the inheritance explicitly进行编译:
class __single_inheritance B;
typedef void (B::*ReceiverFunction)();
似乎没有很多人重现了该问题,我首先在此代码段上展示VS2010的行为。 (调试版本,32位操作系统)
问题出在B::addEven()
和A::addEventListener()
中。为了给我一个检查ESP
值的参考点,在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()
使用ret 10h
清除堆栈,但是只有4个字节被压入堆栈(push offset B::testFunction
),这会导致堆栈帧损坏。
[似乎根据B
是否完成,sizeof(void B::*func())
在VS2010中会发生变化。在OP的代码中,在A.cpp中,B
未完成,并且大小为10h
。在呼叫站点B.cpp中,当B
已完成时,大小变为04h
。 (可以通过sizeof(ReceiverFunction)
进行检查,如上面的代码所示)。这导致在调用站点和实际代码A::addEventListener()
中,增量/参数的大小不相同,从而导致堆栈损坏。
我更改了包含顺序,以确保每个翻译单元中的B
都完整,并且运行时错误消失了。
这应该是VS2010错误...
编译器命令行:
/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
链接器命令行:
/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
我在命令行中隐藏了一些路径。
使用/ vmg作为编译器选项解决了该问题。
但是,我决定改用委托库(http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx),它很好用!