【问题标题】:Member function pointer runtime error - The value of ESP was not properly saved across a function call成员函数指针运行时错误 - ESP 的值未在函数调用中正确保存
【发布时间】:2012-01-30 09:41:31
【问题描述】:

过去一个小时我一直在寻找这个问题的答案,但找不到有效的解决方案。我正在尝试使用函数指针来调用特定对象的非静态成员函数。我的代码编译得很好,但是在运行时我得到一个讨厌的运行时异常,上面写着:

运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是用一个调用约定声明的函数和一个用不同调用约定声明的函数指针调用的结果。

很多网站都说在方法头中指定调用约定,所以我在它前面加了__cdecl。但是,我的代码在更改后遇到了相同的运行时异常(我也尝试使用其他调用约定)。我不确定为什么我必须首先指定 cdecl,因为我的项目设置设置为 cdecl。我正在使用一些外部库,但在我添加这个函数指针之前,它们运行良好。

我正在关注这个:https://stackoverflow.com/a/151449

我的代码:

啊.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
}

ma​​in.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 运行,但我希望我的代码能在其他平台上运行,只需进行少量更改。

【问题讨论】:

  • 当然需要您的代码。但请记住举一个最小的例子。
  • 注意非静态方法调用中的隐藏参数(this指针)
  • 编译器和平台,请。
  • 谢谢大家 - 我已经更新了我的帖子。 @LeleDumbo:我应该如何将“this”与函数指针一起使用?
  • 你调用receiverFunction的代码。最好提供一个显示问题且完整的小型 sn-p,即可以复制粘贴进行测试。

标签: c++ visual-studio-2010 function-pointers member-function-pointers


【解决方案1】:

这是一个known problem,必要的成分是使用不完整类的成员指针声明,并在不同的翻译单元中使用它。 MSVC 编译器中的一种优化,它根据继承对成员指针使用不同的内部表示。

解决方法是使用/vmgdeclare the inheritance explicitly 编译:

class __single_inheritance B;
typedef void (B::*ReceiverFunction)();

【讨论】:

【解决方案2】:

似乎没有多少人重现了这个问题,我先在这里展示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 的 bug ...


编译器命令行:

/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 

我在命令行中隐藏了一些路径。

【讨论】:

  • 啊,这听起来很合理。根据任何函数的继承性和虚拟性,成员函数指针的类型实际上或多或少是复杂的。您可以在this delegate implementation 的源代码中了解它。这很神秘。
  • @Xeo:我听说过这个。我也想知道代码是否按照规范格式正确。
  • @fefe:这似乎仍然是一个错误,因为 VS 应该坚持一种成员函数指针类型。
  • VS 提供了为不完整类型选择最佳或最坏情况成员函数指针大小的选项。大小因潜在的虚拟继承等而有很大差异。
  • @SimonRichter:我用的是默认的,好像是/W3。将添加命令行。
【解决方案3】:

使用 /vmg 作为编译器选项解决了这个问题。

但是,我决定改用委托库 (http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx),效果很好!

【讨论】:

    猜你喜欢
    • 2018-11-28
    • 2011-10-06
    • 1970-01-01
    • 2013-07-03
    • 2015-09-06
    • 2023-03-06
    • 2012-04-22
    • 2013-11-30
    相关资源
    最近更新 更多