Re[6]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 06.06.05 12:29
Оценка:
Здравствуйте, myaso, Вы писали:

M>мысли вслух: полностью соглашаюсь с тем что "невыделение памяти — ошибка фатальная"(и вообще со всем соглашусь), но то что она "лечится только остановкой программы" как-то неочень.....

Безусловно, сильно зависит от окружения (на чем выполняется программа). Если это случилось для Win32 — это означает туши свет. Единственные причины могут быть в том, что обрушилась система управлением памятью, или ее нехватка. И при этом нужно учитывать, что обработку ошибки без выделения памяти сделать очень трудно. Если подобная ситуация прогнозируется, то все значительно полезней и эффективней решается с помощью возможностей стандартной библиотеки или явной работой с Win API функциями. С ее помощью ты можешь построить свой диспетчер памяти который и будет обрабатывать подобные ситуации.

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[7]: try and catch, паранойя в использовании....
От: Cyberax Марс  
Дата: 06.06.05 12:55
Оценка:
GlebZ wrote:

> M>мысли вслух: полностью соглашаюсь с тем что "невыделение памяти —

> ошибка фатальная"(и вообще со всем соглашусь), но то что она "лечится
> только остановкой программы" как-то неочень.....
> Безусловно, сильно зависит от окружения (на чем выполняется
> программа). Если это случилось для Win32 — это означает туши свет.
> Единственные причины могут быть в том, что обрушилась система
> управлением памятью, или ее нехватка.

Еще причиной может быть кастомный аллокатор (например, для расшареной
памяти), у которого закончилась свободная память.

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 1.9
Sapienti sat!
Re[8]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 06.06.05 13:08
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Еще причиной может быть кастомный аллокатор (например, для расшареной

C>памяти), у которого закончилась свободная память.
Каждому кастомному аллокатору свои кастомные правила. Там уже и аля std:exception из аллокатора не грех.

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[9]: try and catch, паранойя в использовании....
От: Cyberax Марс  
Дата: 06.06.05 14:40
Оценка:
GlebZ wrote:

> C>Еще причиной может быть кастомный аллокатор (например, для расшареной

> C>памяти), у которого закончилась свободная память.
> Каждому кастомному аллокатору свои кастомные правила. Там уже и аля
> std:exception из аллокатора не грех.

Стандартом, вообще говоря, предусмотрен std::bad_alloc, который может
вылететь и из стандартного new. Если это не нужно, то следует
использовать new(std::nothow).

--
С уважением,
Alex Besogonov (alexy@izh.com)
Posted via RSDN NNTP Server 1.9
Sapienti sat!
Re[10]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 06.06.05 14:59
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Стандартом, вообще говоря, предусмотрен std::bad_alloc, который может

C>вылететь и из стандартного new. Если это не нужно, то следует
C>использовать new(std::nothow).
+1. Просто упомянул именно std::exception которые родитель всея std исключений. Тогда можно возвращать более информативные ошибки.

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[5]: try and catch, паранойя в использовании....
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.06.05 06:13
Оценка: 1 (1)
Здравствуйте, GlebZ, Вы писали:
GZ>Товарсчь пока на полпути к нирване и помочь ему достигнуть ее — дело каждого пионэра любящего партию и народ. Если не возражаешь, перепишу твой код. А так как у меня и настроение хорошее, даже не буду пользоваться всякими стлными штучками(чтобы показать что все просто).
А-я-яй!
//хотя есть системный assert, возьмем твой
#define assert(x) {if (!(x)) {throw -1;}}

//заделали свой шаблончик
template <class T>
class my_ptr
{
public:
    T* m_t;
    //здесь вместо malloc используем new. Это повысит переносимость.
    my_ptr(int size){m_t=0;m_t=new T[size];assert(m_t!=0);};
    ~my_ptr(){delete[] m_t; m_t=0;}; // для типов с нетривиальным деструктором необходимо корректное убиение!
    T* operator->(){return m_t;};
    T operator*(){return *m_t;};
}; 
//и теперь все стало очень просто
//поскольку невыделение памяти - ошибка фатальная
//то лечится только остановкой программы
void func()
{
    my_ptr<char> pTmp1(100);
    my_ptr<char> pTmp2(100);
}
... << RSDN@Home 1.1.4 beta 5 rev. 395>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 07.06.05 07:03
Оценка:
Здравствуйте, Sinclair, Вы писали:

Thanks за замечание.
Код который никогда не запускался, и никогда не выдавал ошибок, не считается багнутым

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re: try and catch, паранойя в использовании....
От: faulx  
Дата: 07.06.05 09:00
Оценка: 1 (1)
Возможно, поднятая в заголовке сообщения проблема сложнее, чем кажется некоторым из ответивших на него. Для начала приведу некоторые ссылки

1. В этой статье в конце приводятся некоторые соображения по поводу исключений.

2. Из предыдущей статьи есть ссылка на другой небезынтересный материал.

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

Скажем, декларируется, что в .NET все извещения об ошибках осуществляются с помощью исключений, которые все в конечном итоге порождениы от класса System.Exception. (кажется, это не совсем так, но в данном случае на тонкости можно не обращать внимания) Однако перехватывать исключения этого базового класса категорически не рекомендуется (Рихтер в своей книге достаточно убедительно обосновывает, почему). Следовательно, если вызывается некоторый метод некоторого объекта, необходимо выяснить, какие исключения может выбрасывать этот метод и проводить раздельную обработку каждого исключения (если, конечно, по какой-то счастливой случайности они все не имеют одного общего потомка):

void OuterMethod()
{
  try
  {
    obj.InnerMethod(); // Может выбрасывать исключения классов Ex1, Ex2, Ex3 
  }
  catch(Ex1 e)
  {
   processError(e);
  }
  catch(Ex2 e)
  {
   processError(e);
  }
  catch(Ex3 e)
  {
   processError(e);
  }
}


Заметим, что здесь обработка ошибок совершенно идентична для всех трех вариантов. Могут возразить, что эта обработка должна происходить на более высоком уровне, чуть ли не в районе метода Main(). Но это не решает проблемы. Если только один метод может выбрасывать несколько исключений, какое же количество исключений может выбросить вся программа, состоящая из вызовов множества методов! Блоки catch в методе Main() разрастутся до совершенно невероятных размеров. (На практике, как я подозреваю, большинство программистов сдается и все-таки перехватывает System.Exception. Возможные последствия этого достаточно красочно описаны у Рихтера).

Другой недостаток переноса обработки исключений на слишком высокий уровень программы — потеря контекста, в котором произошло исключение. Печально известное исключение NullReferenceException — только один, самый популярный пример. О чем говорит это исключене, пойманное в Main()? Практически ни о чем! Оно может произойти по стольким причинам, что для диагностирования ситуации поможет не больше, чем окошко Access Violation.

Справедливости ради надо сказать, что некоторую помощь окажет, безусловно, stack trace. Однако надо понимать, что stack trace — это инструмент, помогающий только программисту, написавшему программу. Конечный пользователь никакой значимой пользы из него не извлечет, т.е. не поймет, что нужно исправить ему, чтобы сообщение об ошибке перестало появляться.

Решением этой проблемы для пользователя может служить не иерархия вызовов методов (чем и является stack trace), а иерархия выбрасываемых исключений. Для этого при проектировании исключений в .NET была предусмотрена возможность при создании исключения указать другое исключение, послужившее причиной этого. Вот это действительно удобно, поскольку позволяет "протянуть" информацию, доступную только в самом низу иерархии вызовов до того места, где исключение перехватывается, дополняя ее по пути другой информацией.

Например (пример намеренно упрощен и, возможно, не требует таких сложностей), если при инициализации какого-то объекта необходимо выполнить ряд сложных действий (открыть несколько файлов, прочитать их содержимое и т.п.), получение на верхнем уровне исключения с сообщением вроде "Ошибка при разборе строки 'xxx'" (это исключение выбрасывает повторно используемый метод для разбора строки из файла конфигурации, когда этот разбор не может быть осуществлен) заставляет гадать — при разборе какого именно файла произошла ошибка. Если же это исключение будет перехвачено методом, в котором известно имя файла и в качестве реакции будет брошено новое исключение, сообщение об ошибке будет более информативным: "Ошибка при разборе файла 'badfile.ini', причина: "Ошибка при разборе строки 'xxx'"", то мы находимся в более выгодном положении и по-крайней мере знаем место локализации ошибки. Если же добавить еще несколько уровней в иерархии вызовов, различия становятся еще более разительными.

Но при этом теряется то преимущество исключений, что их обработку можно проводить на более высоком уровне! Возвращаясь к приведенному почти в начале этого сообщения коду, можно заключить, что перехват и обработку исключений, бросаемых методом InnerMethod(), нельзя выносить за пределы OuterMethod(). Вместо этого надо сообщить в документации, что OuterMethod() в случае ошибки выбрасывает исключение OuterMethodException, а processError() реализовать как

void processError(Exception e)
{
  throw OuterMethodException("OuterMethod() failed", e);
}


При таком подходе код становится не так уж отличим от кода с явной проверкой возвращаемых значений и кодов ошибок. Но такова, видимо, цена, которую необходимо платить за то, чтобы программа сообщала действительно полезные сведения в случае ошибки.

PS. Была упомянута проблема потери контекста в случае слишком высокого по иерархии перехвата исключений. В связи с этим нельзя не вспомнить подход, применяемый в Common Lisp: Соответствующая глава из Practical Common Lisp.
Re[5]: try and catch, паранойя в использовании....
От: dshe  
Дата: 07.06.05 20:00
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, mihoshi, Вы писали:


GZ>
GZ>#define assert(x) {if (!(x)) {throw -1;}}
GZ>


Не совсем по теме, но мне кажется, что выражать assert, через исключения не совсем корректно. Если утвержение в assert не сработало (т.е. ложно), то программа должна вывести информацию о том, где произошла ошибка (имя файла, номер строки), и свалиться. Свалиться желательно с coredump'ом, по которому потом можно будет восстановить состояние программы в момент возникновения ошибки.

Если в assert'е выбрасываются исключение (независимо от того, -1 или какого-то своего класса), то существует риск того, что это исключение будет как-то отработано (при помощи catch(...)) и информация об ошибке (и о состоянии программы в момент ошибки), будет безвозвратно утеряна.
--
Дмитро
Re[5]: try and catch, паранойя в использовании....
От: McSeem2 США http://www.antigrain.com
Дата: 08.06.05 02:23
Оценка: 1 (1) +1
Здравствуйте, Mika Soukhov, Вы писали:

MS>Это вообще в корне неверно. На основе ошибок работают такие вещи, как: MSMQ (в зависимости от исключений, доставляются данные или нет), компиляторы (продолжается компиляция или все останавливается на этапе разбора текста) или система предупреждения атаки со стороны вредоносных систем (аудиторские системы проводят мониторинг данных, и, если хоть один их элементов не отвечает, то он закрывается от внешнего доступа).


Один мой знакомый писал конечный автомат для какого-то навороченного voice mail на основе исключений. То есть, у него там события передавались при помощи исключений. Получилось очень компактно и работало очень надежно. Ну а быстродействие в таком автомате — вещь не сильно нужная.
McSeem
Я жертва цепи несчастных случайностей. Как и все мы.
Re[6]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 08.06.05 09:24
Оценка:
Здравствуйте, dshe, Вы писали:

D>Не совсем по теме, но мне кажется, что выражать assert, через исключения не совсем корректно. Если утвержение в assert не сработало (т.е. ложно), то программа должна вывести информацию о том, где произошла ошибка (имя файла, номер строки), и свалиться. Свалиться желательно с coredump'ом, по которому потом можно будет восстановить состояние программы в момент возникновения ошибки.

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

D>Если в assert'е выбрасываются исключение (независимо от того, -1 или какого-то своего класса), то существует риск того, что это исключение будет как-то отработано (при помощи catch(...)) и информация об ошибке (и о состоянии программы в момент ошибки), будет безвозвратно утеряна.

Почти да. Рискну сделать два предположения:
1. catch(...) — это плохо. Потому как никогда не знаешь откуда какая ошибка может возникнуть. Можно только предполагать. Следовательно, нужно обрабатывать только те ошибки, о которых имеешь предстваление.
2. в данном случае, мы выполняем assert. Ну а assert используется только в отладочных целях. Взамен лучше ставить конечно явное исключение.
3. Конечно, предложенный Cyberax std::bad_alloc
Автор: Cyberax
Дата: 06.06.05
лучше.

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[6]: try and catch, паранойя в использовании....
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 08.06.05 11:50
Оценка:
Здравствуйте, McSeem2, Вы писали:

MS>Здравствуйте, Mika Soukhov, Вы писали:


MS>>Это вообще в корне неверно. На основе ошибок работают такие вещи, как: MSMQ (в зависимости от исключений, доставляются данные или нет), компиляторы (продолжается компиляция или все останавливается на этапе разбора текста) или система предупреждения атаки со стороны вредоносных систем (аудиторские системы проводят мониторинг данных, и, если хоть один их элементов не отвечает, то он закрывается от внешнего доступа).


MS>Один мой знакомый писал конечный автомат для какого-то навороченного voice mail на основе исключений. То есть, у него там события передавались при помощи исключений. Получилось очень компактно и работало очень надежно. Ну а быстродействие в таком автомате — вещь не сильно нужная.


+1

AFAIK, сам Страуструп в книге "Язык программирования C++" описывал возможность применения исключений подобным образом.
... << RSDN@Home 1.1.4 beta 7 rev. 447>>


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[7]: try and catch, паранойя в использовании....
От: stalcer Россия  
Дата: 08.06.05 12:39
Оценка: +1
Здравствуйте, GlebZ, Вы писали:

GZ>1. catch(...) — это плохо. Потому как никогда не знаешь откуда какая ошибка может возникнуть. Можно только предполагать. Следовательно, нужно обрабатывать только те ошибки, о которых имеешь предстваление.


Плохо не catch(...), а гашение исключений при помощи catch(...). Если catch(...) исплоьзуется так:

try
{
    SomeFunc(...);
} 
catch (...)
{
    // Do nothing. No rethrow.
};

то это, безусловно, плохо.

Но иногда я ловлю все исключения на границе какой-либо подсистемы, например, так:

try
{
    doCompile();
}
catch (const CompileException &e)
{
    throw; // Just rethrow.
}
catch (const std::bad_alloc &e)
{
    throw CompileException("Not enought memory for compilation.");
}
catch (...)
{
    throw CompileException("Internal compilation error.");
}

Не вижу в этом ничего плохого.
http://www.lmdinnovative.com (LMD Design Pack)
Re[7]: try and catch, паранойя в использовании....
От: mihoshi Россия  
Дата: 08.06.05 12:46
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>1. catch(...) — это плохо. Потому как никогда не знаешь откуда какая ошибка может возникнуть. Можно только предполагать. Следовательно, нужно обрабатывать только те ошибки, о которых имеешь предстваление.


Он используется исключительно чтобы освободить выделенные ресурсы и сделать throw дальше.
Re[6]: try and catch, паранойя в использовании....
От: mihoshi Россия  
Дата: 08.06.05 12:49
Оценка:
Здравствуйте, dshe, Вы писали:

GZ>>
GZ>>#define assert(x) {if (!(x)) {throw -1;}}
GZ>>


D>Не совсем по теме, но мне кажется, что выражать assert, через исключения не совсем корректно. Если утвержение в assert не сработало (т.е. ложно), то программа должна вывести информацию о том, где произошла ошибка (имя файла, номер строки), и свалиться. Свалиться желательно с coredump'ом, по которому потом можно будет восстановить состояние программы в момент возникновения ошибки.


Если я переименую assert в verify, это поможет?
Re[8]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 08.06.05 12:59
Оценка:
Здравствуйте, stalcer, Вы писали:

Насчет rethrow согласен. Работаю на нескольких языках, в большинстве случаев finally хватает. Сорри. Сначала написал, а потом подумал об этом случае, но он вполне понятен и не очень связан с предыдущей ситуацией.

S>Но иногда я ловлю все исключения на границе какой-либо подсистемы, например, так:


S>
S>try
S>{
S>    doCompile();
S>}
S>catch (const CompileException &e)
S>{
S>    throw; // Just rethrow.
S>}
S>catch (const std::bad_alloc &e)
S>{
S>    throw CompileException("Not enought memory for compilation.");
S>}
S>catch (...)
S>{
S>    throw CompileException("Internal compilation error.");
S>}
S>

S>Не вижу в этом ничего плохого.
Конечно все зависит от конкретной ситуацией. Если это обозначает полное обновление состояние данной подсистемы после поимки исключения, то это нормально. Если состояние неубивается то адекватность дальнейшей работы уже непонятна.

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Re[7]: try and catch, паранойя в использовании....
От: dshe  
Дата: 08.06.05 20:03
Оценка: 4 (2) +1
Здравствуйте, mihoshi, Вы писали:

M>Здравствуйте, dshe, Вы писали:


GZ>>>
GZ>>>#define assert(x) {if (!(x)) {throw -1;}}
GZ>>>


D>>Не совсем по теме, но мне кажется, что выражать assert, через исключения не совсем корректно. Если утвержение в assert не сработало (т.е. ложно), то программа должна вывести информацию о том, где произошла ошибка (имя файла, номер строки), и свалиться. Свалиться желательно с coredump'ом, по которому потом можно будет восстановить состояние программы в момент возникновения ошибки.


M>Если я переименую assert в verify, это поможет?


Нет. Дело совсем не в этом. Так исторически сложилось, что assert (и verify) служит для проверки правильности программы. Если условие в нем ложно, то это означает, что программист что-то неправильно написал. С другой стороны исключительные ситуации -- это обычно реакция программы на необычное поведение внешней среды. Исключительные ситуации не предназначены для того, чтобы сигнализировать о том, что произошла какая-то программная ошибка. Исключительные ситуации подразумевают какое-то восстановление после них. После программных ошибок программа не может корректно восстановиться -- даже если она будет продолжать работать, она будет работать неправильно. Самое лучшее, что она (программа) может в данном случае сделать, это завершить свою работу и дать программисту максимум информации о своем предсмертном состоянии.

В одном из проектов, в котором я учавствовал, assert бросал исключения. Эти исключения потом на самом верхнем уровне ловились и в лог писалось что-то вроде "Internal Error", "Unknown Error", "Something wrong" или "Shit happened". Пользы от подобных сообщений было -- ноль! Зато программа делала вид, что работает.
--
Дмитро
Re[9]: try and catch, паранойя в использовании....
От: IT Россия linq2db.com
Дата: 09.06.05 02:07
Оценка:
Здравствуйте, GlebZ, Вы писали:

S>>Но иногда я ловлю все исключения на границе какой-либо подсистемы, например, так:


А в чём поинт? Почему не написать просто так:

try
{
    doCompile();
}
catch (const std::bad_alloc &e)
{
    throw CompileException("Not enought memory for compilation.");
}
... << RSDN@Home 1.1.4 beta 7 rev. 447>>
Если нам не помогут, то мы тоже никого не пощадим.
Re[10]: try and catch, паранойя в использовании....
От: stalcer Россия  
Дата: 09.06.05 07:22
Оценка: 2 (2) +1
Здравствуйте, IT, Вы писали:

IT>А в чём поинт? Почему не написать просто так:


IT>
IT>try
IT>{
IT>    doCompile();
IT>}
IT>catch (const std::bad_alloc &e)
IT>{
IT>    throw CompileException("Not enought memory for compilation.");
IT>}
IT>


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

И потом, фактически у тебя нет шанса/смысла обработать левые исключения, выброшенные из этой подсистемы, потому как в соответствии с инкапсуляцией ты не знаешь как она устроена и почему именно это исключение возникло. А даже если и знаешь, то это исключение означает лишь одно, — что при компиляции произошол сбой, которого быть не должно и процесс компиляции прекращен.
http://www.lmdinnovative.com (LMD Design Pack)
Re[10]: try and catch, паранойя в использовании....
От: GlebZ Россия  
Дата: 09.06.05 08:00
Оценка:
Здравствуйте, IT, Вы писали:

IT>А в чём поинт? Почему не написать просто так:

IT>
IT>try
IT>{
IT>    doCompile();
IT>}
IT>catch (const std::bad_alloc &e)
IT>{
IT>    throw CompileException("Not enought memory for compilation.");
IT>}
IT>

Можешь. Но в данном случае разговор о catch(...).

С уважением, Gleb.
... << RSDN@Home 1.1.4 beta 4 rev. 358>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.