C++ 成员函数指针运行时错误-未在函数调用中正确保存ESP的值
在过去的一个小时里,我一直在寻找这个问题的答案,但找不到有效的解决方案。我试图使用函数指针来调用特定对象的非静态成员函数。我的代码编译得很好,但在运行时我遇到了一个令人讨厌的运行时异常,它说: 运行时检查失败#0-未在函数调用中正确保存ESP的值。这通常是调用使用一个调用约定声明的函数以及使用另一个调用约定声明的函数指针的结果 很多网站都说在方法头中指定了调用约定,所以我在前面添加了C++ 成员函数指针运行时错误-未在函数调用中正确保存ESP的值,c++,visual-studio-2010,function-pointers,member-function-pointers,C++,Visual Studio 2010,Function Pointers,Member Function Pointers,在过去的一个小时里,我一直在寻找这个问题的答案,但找不到有效的解决方案。我试图使用函数指针来调用特定对象的非静态成员函数。我的代码编译得很好,但在运行时我遇到了一个令人讨厌的运行时异常,它说: 运行时检查失败#0-未在函数调用中正确保存ESP的值。这通常是调用使用一个调用约定声明的函数以及使用另一个调用约定声明的函数指针的结果 很多网站都说在方法头中指定了调用约定,所以我在前面添加了\uuu cdecl。但是,我的代码在更改后遇到了相同的运行时异常(我也尝试使用其他调用约定)。我不确定为什么必须
\uuu cdecl
。但是,我的代码在更改后遇到了相同的运行时异常(我也尝试使用其他调用约定)。我不确定为什么必须首先指定cdecl,因为我的项目设置设置为cdecl。我正在使用一些外部库,但在我添加函数指针之前,这些库工作得很好
我是这样说的:
我的代码:
A.h
#pragma once
class B;
typedef void (B::*ReceiverFunction)();
class A
{
public:
A();
~A();
void addEventListener(ReceiverFunction receiverFunction);
};
#pragma once
#include <iostream>
#include "A.h"
class B
{
public:
B();
~B();
void testFunction();
void setA(A* a);
void addEvent();
private:
A* a;
};
A.cpp
#include "A.h"
A::A(){}
A::~A(){}
void A::addEventListener(ReceiverFunction receiverFunction)
{
//Do nothing
}
#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
}
#include "A.h"
#include "B.h"
int main()
{
A* a = new A();
B* b = new B();
b->setA(a);
b->addEvent();
}
B.h
#pragma once
class B;
typedef void (B::*ReceiverFunction)();
class A
{
public:
A();
~A();
void addEventListener(ReceiverFunction receiverFunction);
};
#pragma once
#include <iostream>
#include "A.h"
class B
{
public:
B();
~B();
void testFunction();
void setA(A* a);
void addEvent();
private:
A* a;
};
main.cpp
#include "A.h"
A::A(){}
A::~A(){}
void A::addEventListener(ReceiverFunction receiverFunction)
{
//Do nothing
}
#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
}
#include "A.h"
#include "B.h"
int main()
{
A* a = new A();
B* b = new B();
b->setA(a);
b->addEvent();
}
我使用的是Visual Studio 2010,但我希望我的代码能够在其他平台上运行,并且改动最小。似乎没有多少人重现了这个问题,我将首先在这段代码上展示VS2010的行为。(调试构建,32位操作系统) 问题出在
B::addEven()
和A::addEventListener()
中。为了给我一个检查ESP
值的参考点,在B::addEven()
中添加了两个附加语句
A::addEventListener()
使用ret 10h
清除堆栈,但只将4个字节推入堆栈(push offset B::testFunction
),这会导致堆栈帧损坏
似乎取决于B
是否完整,sizeof(void B::*func())
在VS2010中会发生变化。在OP的代码中,在A.cppB
中未完成,大小为10h
。在呼叫站点B.cpp中,当B
已完成时,大小变为04h
。(这可以通过sizeof(ReceiverFunction)
进行检查,如上述代码所示)。这导致在调用站点中,以及在A::addEventListener()
的实际代码中,augment/参数的大小不同,从而导致堆栈损坏
我更改了包含顺序,以确保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
链接器命令行:
/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
我在命令行中隐藏了一些路径。这是一个必需的组件,它是一个使用不完整类的成员指针声明,并在不同的转换单元中使用。MSVC编译器中的一种优化,它根据继承为成员指针使用不同的内部表示形式
解决方法是使用/vmg
进行编译,或者:
使用/vmg作为编译器选项修复了该问题
但是,我决定改用委托库(),它工作得很好 当然,您的代码是必需的。但请记住给出一个简单的例子。请注意非静态方法callCompiler and platform中的隐藏参数(
this
pointer)。谢谢各位,我已经更新了我的帖子@LeleDumbo:我应该如何在函数指针中使用“this”?调用receiverFunction
的代码。最好提供一个小片段来展示问题,并且是完整的,也就是说,可以复制粘贴以进行测试。啊,这听起来很有道理。根据任何函数的继承性和虚拟性,成员函数指针的类型实际上或多或少是复杂的。您可以在的源代码中阅读。这很神秘。@Xeo:我听说过。我还想知道根据规范,代码的格式是否正确。@fefe:这似乎仍然是一个bug,因为VS应该坚持一个成员函数指针类型。VS可以选择不完整类型的最佳或最坏情况的成员函数指针大小。大小根据潜在的虚拟继承和类似的东西而变化很大。@SimonRichter:我使用了默认值,似乎是/W3。将添加命令行。似乎是第一个URL的当前工作链接。Connect网站已停止,其文章没有回程机器备份。没关系,专注于解决方案。