Ошибка выполнения указателя функции-члена - значение ESP не было должным образом сохранено при вызове функции.

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

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


person Petwoip    schedule 30.12.2011    source источник
comment
Конечно, нужен ваш код. Но не забудьте привести минимальный пример.   -  person fefe    schedule 30.12.2011
comment
следите за скрытым аргументом (указатель this) при вызове нестатического метода   -  person LeleDumbo    schedule 30.12.2011
comment
Компилятор и платформу, пожалуйста.   -  person lapk    schedule 30.12.2011
comment
Спасибо, ребята, я обновил свой пост. @LeleDumbo: Как мне использовать это с указателями на функции?   -  person Petwoip    schedule 30.12.2011
comment
Код, в котором вы вызываете receiverFunction. Лучше всего предоставить небольшой фрагмент, который демонстрирует проблему и является полным, т. Е. Может быть скопирован для тестирования.   -  person Xeo    schedule 30.12.2011
comment
Я сейчас нигде не вызываю метод ReceiverFunction. Я оставил метод addEventListener пустым. Я добавлю код вызова, когда все остальное сработает.   -  person Petwoip    schedule 30.12.2011
comment
Тогда о каком вызове функции среда выполнения выдает ошибку?   -  person Michael Burr    schedule 30.12.2011
comment
Тогда ваш код выглядит примерно так?   -  person Xeo    schedule 30.12.2011
comment
Я создал пример проекта и обновил весь код в моем вопросе. Та же проблема, что и раньше.   -  person Petwoip    schedule 30.12.2011
comment
Я не получаю ошибок при запуске предоставленного кода. : / (Я действительно поместил все это в один файл cpp, но теоретически это не должно иметь значения.) Может быть, что-то не так с вашей установкой Visual Studio?   -  person Xeo    schedule 30.12.2011
comment
Не могли бы вы прислать мне код, который вы использовали? Кроме того, вы работаете в Windows VS2010?   -  person Petwoip    schedule 30.12.2011
comment
Я просто попробовал свой код в Code :: Blocks (который использует MinGW), и мой код работал нормально. Что насчет Visual Studio может вызвать эту проблему?   -  person Petwoip    schedule 30.12.2011
comment
Боже мой, такая же ошибка с OP в VS 2010 ...   -  person fefe    schedule 30.12.2011


Ответы (3)


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

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

class __single_inheritance B;
typedef void (B::*ReceiverFunction)();
person Hans Passant    schedule 30.12.2011
comment
social.msdn.microsoft.com/Forums/en-US/ кажется действующей в настоящее время ссылкой первого URL-адреса. - person Marvin; 13.09.2019
comment
Веб-сайт Connect больше не работает, так как их статьи не имеют резервных копий Wayback Machine. Неважно, сосредоточьтесь на решении. - person Hans Passant; 13.09.2019

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

Я спрятал некоторые пути в командной строке.

person fefe    schedule 30.12.2011
comment
Ах, это действительно звучит правдоподобно. В зависимости от наследования и виртуальности любых функций тип указателя на функцию-член на самом деле более или менее сложен. Вы можете прочитать об этом в исходном коде этой реализации делегата. Это загадочно. - person Xeo; 30.12.2011
comment
@Xeo: Я слышал об этом. Мне также интересно, правильно ли составлен код согласно спецификации. - person fefe; 30.12.2011
comment
@fefe: это все еще кажется ошибкой, потому что VS должен придерживаться одного типа указателя на функцию-член. - person Xeo; 30.12.2011
comment
VS имеет варианты выбора наилучшего или наихудшего размера указателя функции-члена для неполных типов. Размер сильно варьируется в зависимости от потенциального виртуального наследования и тому подобного. - person Bo Persson; 30.12.2011
comment
@SimonRichter: Я использовал значение по умолчанию, похоже, / W3. Добавлю командную строку. - person fefe; 30.12.2011

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

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

person Petwoip    schedule 31.12.2011