Разматывание стека в случае структурированных исключений - PullRequest
4 голосов
/ 06 марта 2009

Этот вопрос дает больше ясности по проблеме, описанной здесь . Я провел еще одно исследование и обнаружил, что раскручивание стека не происходит в следующем фрагменте кода:

class One
{
public:
    int x ;
};

class Wrapper
{
public:
    Wrapper(CString csText):mcsText(csText)
    {
        CString csTempText;
        csTempText.Format("Wrapper constructor :: %s\n", mcsText);
        OutputDebugString(csTempText);
    }

    ~Wrapper()
    {
        CString csTempText;
        csTempText.Format("Wrapper destructor :: %s\n", mcsText);
        OutputDebugString(csTempText);
    }
    CString mcsText;
};
class Test
{
    public:

    void notifyError()
    {
        try
        {
            int x = 10; 
        }
        catch(...)  {}
    }

    void OnRecvBuffer()
    {
        try
        {
            Wrapper a("AddRef");    
            One* p = NULL;
            p->x = 10;
        }
        catch(...)
        {
            notifyError();
        }   
    }   
};



int main() 
{
    Test* pTest = new Test;

    pTest->OnRecvBuffer();

    OutputDebugString("Test");
}

Я скомпилировал этот код с использованием компилятора VC6 SP5, и на выходе получился "Конструктор Wrapper :: AddRef !!!" (т.е. деструктор объекта-обертки, который был создан на стеке, не вызывается. Это ожидаемое поведение? или это ошибка в компиляторе VC? Могу ли я использовать некоторые флаги компилятора, чтобы в этом случае происходило разматывание стека?

Ответы [ 4 ]

7 голосов
/ 06 марта 2009

Стандарт C ++ не дает ничего для работы в случае неопределенного поведения. Даже если MS делает. Это зависит от платформы - так что будьте осторожны. Некоторые такие исключения с плавающей точкой превращаются в исключения Win32, которые вы можете попытаться отловить с помощью _set_se_translator(). Проблема в том, что вы можете перехватить исключения Win32, но тогда ваш стек не будет правильно разматываться. По крайней мере, это не то, на что вы можете поставить свою жизнь. В чем тщетность этого упражнения.

Обновление: исключение выдается преднамеренно, чтобы проверить разматывание стека. Вопрос в том, почему деструктор класса Wrapper не вызывается. - Навин

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

например:

void OnRecvBuffer()
{
    try
    {
        Wrapper a("AddRef");    
        throw 42; // this'll throw an exception without invoking UB
    }
    catch(...)
    {
        notifyError();
    }
}

Вы не можете разыменовать нулевой указатель. Вы используете неопределенное поведение здесь:

One* p = NULL;
p->x = 10;

После этой линии все ставки сняты, и вы могли бы убить нас всех;)

p - указатель на One объект. Он должен содержать адрес объекта One. Вы инициализировали его как 0 - по адресу 0. объекта нет. 0 не является допустимым адресом для какого-либо объекта (это гарантируется стандартом).

4 голосов
/ 06 марта 2009

Если вы хотите использовать SEH, вы должны использовать функцию _set_se_translator и опцию компилятора / EHa.

2 голосов
/ 06 марта 2009

Поскольку обычное исключение C ++ не обрабатывает такого рода исключения, вы должны использовать SEH, который ничего не знает о приложении и не раскручивает.

0 голосов
/ 06 марта 2009

Это неопределенное поведение:

One* p = NULL;
p->x = 10;

В этот момент приложение может завершить работу без разматывания стека.
Если вы хотите проверить раскрутку стека, замените это на:

 throw 42; // Life the Universe and Everything thrown away

Не следует динамически выделять все ваши объекты, это C ++, а не Java!

int main() 
{
    Test    pTest;   // Note the lack of new!

    pTest.OnRecvBuffer();

    OutputDebugString("Test");
}
...