#include <exception> #include <iostream> #include <eh.h> #include <windows.h> void trans_func( unsigned int u, EXCEPTION_POINTERS* p ) { std::cout << "trans_func called" << std::endl; if (p->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) throw std::runtime_error("access violation"); throw std::runtime_error("some other error"); } class Foo { public: virtual void bar() { std::cout << ":)" << std::endl; } }; int main(int argc, char* argv[]) { _set_se_translator(&trans_func); Foo *foo = NULL; try { foo->bar(); } catch(std::exception& e) { std::cout << "error handled: " << e.what() << std::endl; } system("pause"); return 0; }Этот код выведет сообщение об ошибке. Теперь о моральных аспектах проблеммы. Во первых, использование опции /Eha снижает общую производительность, так как компилятор «не знает» о том, где может быть выброшено исключение, а где его в принципе быть не может. Во вторых выбрасывание исключения при разименовании указателя или при порче стэка — не стандартное поведение, к примру разименование нулевого указателя, это undefined behavior. Поэтому программа, которую я привел в качестве примера, не является корректной с точки зрения стандарта, но зато работает :).
вторник, 1 июля 2008 г.
Обработка исключений и корректность программ на С++.
Я снова хочу затронуть тему обрабртки исключений. На этот раз речь пойдет о структурной обработке исключений операционной системы windows — SEH.
Стандарт описывает только модель обработки исключений, не зависящую от платформы, под которую программа компилируется. Такое исключение может быть выброшено с помощью ключевого слова throw, функцией стандартной библиотеки, или оператором new... Исключение имеет тип, который определяет то, какой обработчик будет вызван, а так-же то, какую информацию исключение передаст в свой обработчик.
Помимо этого существуют еще структурные исключения, они не зависят от языка программирования, а специфичны для операционной системы. Такое исключение может быть выброшено при попытке поделить на ноль, или ошибке доступа к памяти и так далее. Компилятор Visual Studio имеет такую опцию как /Eha, которая позволяет программе использовать SEH.
Использовать одновременно исключения обоих типов, в программе на С++ проблематично, так как прийдется их обрабатывать по отдельности. Что-бы этого избежать SEH исключение нужно транслировать в обычное исключение. Делается это с помощью функции _set_se_translator стандартной библиотеки, сама эта функция стандартной не является. Она получает указатель на функцию транслятор, которая получает структуру описывающую исключение и в ответ, должна бросить типизированное исключение, вот как-то так:
кстати говоря, следует обратить внимание что если уж юзать SEH (например, для краш репортинга), то нужно ещё хотя бы set_terminate(), set_unexpected(), _set_purecall_handler() и _set_invalid_parameter_handler().
ОтветитьУдалитьА иначе такая простая конструкция как, скажем, std::vector<int>v;v.front(); повалит программу несмотря на любую обработку исключений.
А всё потому что http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=101337
Ещё интересным нюансом является то, что плохо обрабатывать SEH в контексте того исполняемого файла, где произошло исключение. Дело в том что SEH-исключение запросто может быть вызвано нарушением кучи, вследсвтие чего обработчик точно так же упадёт из-за тех же нарушений в куче -- при малейшем динамическом управлении памятью. Чтобы этого не произошло, обработчик SEH ложат в отдельную DLL... но в общем случае он уже мало что может сделать для спасения главной проги -- разве что красиво попросить прощения :)
Вообще в таких случаях не очень понятно как следует поступать. Вот, допустим программа перехватила Access Violation, и что, продолжить работать дальше? Тут остается только запись что-нибудь в лог, создать crash report и подохнуть :)
ОтветитьУдалитьК тому-же это решение не будет кроссплатформенным...
да, единственное что правильно сделать в обработчике SEH -- это красиво и с песнями умереть :)
ОтветитьУдалитьо кроссплатформенности -- пусть это и не переносимо на уровне кода, но концепция работает и в линухе -- там тоже можно обрабатывать AccessViolation и иже с ними, через catch(...) + signal()
прикольная фича которую доводилось реализовать -- в SEH обработчике сервера запомнить последний запрос, перезапуститься и таки его обработать :) Некрасиво конечно, но спасло на здоровенном чужом проекте, когда дедлайн прижал а оно изредка падало. Кажется, та система до сих пор крутится на серверах заказчика, и никто даже не подозревает что сервер падает каждых полчаса :)))
Мне все-же кажется, что лучше SEH не обрабатывать вообще, производительность чуть выше, крэшдамп можно и без этого сформировать, а для чего еще их перехватывать кроме диагностики я не представляю...
ОтветитьУдалитьну конечно кроме таких экзотических случаев :))
ОтветитьУдалитьSEH может пригодится тому, кто решится под Windows эмулировать какую-нибудь другую среду (например Linux). Так как позволяет отловить вызовы прерываний, обращений по несуществуещему адресу и т.д.
ОтветитьУдалитьВообще в таких случаях не очень понятно как следует поступать. Вот, допустим программа перехватила Access Violation, и что, продолжить работать дальше? Тут остается только запись что-нибудь в лог, создать crash report и подохнуть :)
ОтветитьУдалитьК тому-же это решение не будет кроссплатформенным...