Ошибка выполнения указателя на функцию-член - значение ESP не было должным образом сохранено при вызове функции - PullRequest
2 голосов
/ 30 декабря 2011

Я искал ответы на эту проблему в течение последнего часа, но не могу найти решение, которое работает. Я пытаюсь использовать указатели на функции для вызова нестатической функции-члена определенного объекта. Мой код компилируется нормально, но во время выполнения я получаю неприятное исключение времени выполнения, которое говорит:

Ошибка проверки времени выполнения # 0 - значение ESP не было должным образом сохранено при вызове функции. Обычно это является результатом вызова функции, объявленной с одним соглашением о вызовах с указателем функции, объявленным с другим соглашением о вызовах.

На многих сайтах указывается соглашение о вызовах в заголовке метода, поэтому я добавил __cdecl перед ним. Тем не менее, мой код столкнулся с тем же исключением во время выполнения после изменения (я также пытался использовать другие соглашения о вызовах). Я не уверен, почему я должен указать cdecl в первую очередь, потому что мои настройки проекта установлены на cdecl. Я использую некоторые внешние библиотеки, но они работали нормально до того, как я добавил эту функцию указателя.

Я слежу за этим: https://stackoverflow.com/a/151449

Мой код:

хиджры

#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, но мне бы хотелось, чтобы мой код работал на других платформах с минимальными изменениями.

Ответы [ 3 ]

8 голосов
/ 30 декабря 2011

Это известная проблема , необходимые ингредиенты - это объявление указателя на член, использующее неполный класс и использующее его в разных единицах перевода. Оптимизация в компиляторе MSVC, она использует различные внутренние представления для указателей на член в зависимости от наследования.

Обходной путь - скомпилировать с /vmg или объявить наследование явно :

class __single_inheritance B;
typedef void (B::*ReceiverFunction)();
4 голосов
/ 30 декабря 2011

Кажется, не многие воспроизвели проблему, я сначала покажу поведение VS2010 на этом фрагменте кода здесь.(DEBUG build, 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 голосов
/ 31 декабря 2011

Использование / vmg в качестве опции компилятора решило проблему.

Однако я решил вместо этого использовать библиотеку делегатов (http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx),, и она работает хорошо!

...