成员函数指针运行时错误-在函数调用中未正确保存ESP的值

问题描述 投票:2回答:3

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

运行时检查失败#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一起运行,但是我希望我的代码能够在其他平台上以最少的更改来工作。

c++ visual-studio-2010 function-pointers member-function-pointers
3个回答
8
投票

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

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

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

4
投票

似乎没有很多人重现了该问题,我首先在此代码段上展示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 

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


0
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.