Russian Qt Forum

Программирование => С/C++ => Тема начата: Igors от Март 28, 2015, 08:09



Название: Прелести исключений
Отправлено: Igors от Март 28, 2015, 08:09
Добрый день

Не ловится исключение в совершенно безобидном коде
Код
C++ (Qt)
try {
 data.ref = new MyClass(id, type);
}
catch (...) {
delete data.ref;
data.ref = 0;
}
Получаю SIGABORT, отладчик выбрасывает меня в совершенно посторонний исходник. Мои действия

- поменял исключение (что выбрасывает MyClass) на std::runtime_error. Ничего не изменилось
- делаю throw первым в конструкторе MyCass - тот же вылет
- вставил throw перед new - все норм, так ловится
- заменил MyClass на свой (так же с throw в конструкторе) - все норм, так ловится
- пошел по шагам в ассемблере - следы теряются на вызове throw

Выдрать MyClass в тестовый пример не удается, он завязан на кучу других, поэтому "код в студию" не получится. Что можно предпринять?

Спасибо


Название: Re: Прелести исключений
Отправлено: kamre от Март 28, 2015, 12:56
Может быть такое: во время выброса исключения из конструктора MyClass происходит вызов деструкторов для уже созданных его членов, и происходит выброс исключения из деструктора какого-то члена класса MyClass?


Название: Re: Прелести исключений
Отправлено: _Bers от Март 28, 2015, 15:54
Добрый день

Не ловится исключение в совершенно безобидном коде
Код
C++ (Qt)
try {
 data.ref = new MyClass(id, type);
}
catch (...) {
delete data.ref;
data.ref = 0;
}
Получаю SIGABORT, отладчик выбрасывает меня в совершенно посторонний исходник. Мои действия

- поменял исключение (что выбрасывает MyClass) на std::runtime_error. Ничего не изменилось
- делаю throw первым в конструкторе MyCass - тот же вылет
- вставил throw перед new - все норм, так ловится
- заменил MyClass на свой (так же с throw в конструкторе) - все норм, так ловится
- пошел по шагам в ассемблере - следы теряются на вызове throw

Выдрать MyClass в тестовый пример не удается, он завязан на кучу других, поэтому "код в студию" не получится. Что можно предпринять?

Спасибо

код вовсе не безобидный, как вы думаете.
рассмотрим пример:

Код:
#include <iostream>

struct MyClass
{
    MyClass(int, int)
    {
        throw 1;
    }
   
};

int main()
{
    std::cout << "Hello, world!\n";
   
    MyClass* ptr = nullptr;
    const int id = 1;
    const int type = 1;
   
    try {

      auto* ptr = new MyClass(id, type);
    }
    catch (...) {
     std::cout<<"address = "<< ptr<<std::endl;
    }
}

вывод:
Цитировать
Hello, world!
address = 0

Что произошло:


1.
new-expression
сначала выделила память под объект.
затем выполнила запуск конструктора класс.

2. из конструктора полетело исключение.
не был вызван диструктор недостроенного объекта.
вся выделенная память вернулась системе
принимающий указатель остался без изменений.

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

что происходит в вашем случае:

Код:
try {
  //вылетает исключение, и data.ref остается без изменений
  //в вашем случае вероятно он содержит какой то мусор
  data.ref = new MyClass(id, type);
}
catch (...) {

  // вот здесь вы пытаетесь удалить память, которая не была выделена
  // и получаете от системы по заднице
 delete data.ref;
 data.ref = 0;
}

ваш код выполняет неправомерную попытку удаления чужой памяти.


Название: Re: Прелести исключений
Отправлено: Igors от Март 28, 2015, 16:54
Может быть такое: во время выброса исключения из конструктора MyClass происходит вызов деструкторов для уже созданных его членов, и происходит выброс исключения из деструктора какого-то члена класса MyClass?
Смотрел, там только POD члены - ну и сам он наследник QImage. А главное - деструктор не вызывается зовется исключением если оно выброшено из конструктора.

что происходит в вашем случае:
Код:
  // вот здесь вы пытаетесь удалить память, которая не была выделена
  // и получаете от системы по заднице
Ну программист который писал это в 90-х (старый код) конечно занулил указатель еще до try, поэтому здесь утечка (и то не на всех компиляторах). И входа в catch не происходит, ставил и печать перед delete и DebugStr - получаю SIGABORT точно "до того".

После неск часов метаний пришла мысля - просто сменил компилятор для этого файла (icc на сlang) . Все работает :) Еще неск часов - и нашел icc опцию которая гадила. Потом все-таки убрал throw из конструктора - ни к чему оно там, да и утечка. И все бы ничего если бы не много потерянных часов  :'(


Название: Re: Прелести исключений
Отправлено: _Bers от Март 28, 2015, 20:18
Ну программист который писал это в 90-х (старый код) конечно занулил указатель еще до try, поэтому здесь утечка (и то не на всех компиляторах).

какая ещё утечка? там не может быть утечеки.

Еще неск часов - и нашел icc опцию которая гадила.

в будущем, кто-то столкнется спободной проблемой.
будет гуглить.
наткнется на эту тему.
и это все, что он узнает: "есть какая то опция".


Название: Re: Прелести исключений
Отправлено: Igors от Март 29, 2015, 08:27
какая ещё утечка? там не может быть утечеки.
Простая
Код
C++ (Qt)
data.ref = new MyClass(id, type);
Компилятор может установить значение data.ref после выделения блока памяти но еще до выполнения конструктора. Но может и нет, более того, такая установка - капитальный геморрой с multi-threadng. Некоторые имеют опцию (как-то со словом new, точно не помню), другие (напр icc) просто этого не делают. Не исключено что новом стандарте это запрещено. Поэтому последующее delete (в catch) до выделенного блока не дотянется. 


Название: Re: Прелести исключений
Отправлено: _Bers от Март 29, 2015, 10:10
какая ещё утечка? там не может быть утечеки.
Простая
Код
C++ (Qt)
data.ref = new MyClass(id, type);
Компилятор может установить значение data.ref после выделения блока памяти но еще до выполнения конструктора. Но может и нет, более того, такая установка - капитальный геморрой с multi-threadng. Некоторые имеют опцию (как-то со словом new, точно не помню), другие (напр icc) просто этого не делают. Не исключено что новом стандарте это запрещено. Поэтому последующее delete (в catch) до выделенного блока не дотянется. 

память выделяется до запуска конструктора.
если конструктор бросит исключение, то вся память будет возвращена системе.
никаких утечек.

если указатель был объявлен до секции try, то он останется без изменений.
если в секции try, то будучи автоматическим объектом прекратит существование.

причем тут multi-threadng я вообще не понял.
но однозначно, он не имеет никакого отношения ни к эксепшенам,
ни к операции выделения памяти.


Название: Re: Прелести исключений
Отправлено: Igors от Март 29, 2015, 10:23
если конструктор бросит исключение, то вся память будет возвращена системе.
Первый раз слышу. Покажите где это написано. Спасибо

причем тут multi-threadng я вообще не понял.
Пример
Код
C++ (Qt)
MyClass *  MyClass::Instance( void )
{
if (!mInstance) {
 QMutexLocker lock(&mutex);
 if (!mInstance)
  mInstance = new MyClass;
}
return mInstance;
}
Это неверно с 2 и более нитками по той причине что (во всяком случае в старом стандарте) не запрещается присвоить значение mInstance до того как конструктор отработал.


Название: Re: Прелести исключений
Отправлено: _Bers от Март 29, 2015, 11:43
если конструктор бросит исключение, то вся память будет возвращена системе.
Первый раз слышу. Покажите где это написано. Спасибо

Здрасти, приехали.
Вообще то об этом пишется во всех книжках для новичков.

А выше я вам привел рабочий пример-иллюстрацию, который показывает,
что после исключения указатель не изменился

Цитировать
5.3.4 New [expr.new]
20. If any part of the object initialization described above (78) terminates by throwing an exception, storage has
been obtained for the object, and a suitable deallocation function can be found, the deallocation function is
called to free the memory in which the object was being constructed
, after which the exception continues to
propagate in the context of the new-expression

78) This may include evaluating a new-initializer and/or calling a constructor.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf


Совершенно очевидно, что сделано это по одной простой причине:

Вы имеете полное право написать код:

Код:
try
{
    auto* p = new some;
}
catch(...)
{
    //ну и как теперь прикажите теперь ловить утечки???
}


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

И тем самым бы нарушилось фундаментальное:
"можно объявлять переменные по месту использования".

с++ это вам не паскаль, где все переменные обязательно нужно объявлять в начале блока.

Пример

Понял.

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

Не является спецификой операции выделения памяти, и не имеет к этому отношения.


Название: Re: Прелести исключений
Отправлено: Igors от Март 29, 2015, 13:04
Здрасти, приехали.
Вообще то об этом пишется во всех книжках для новичков.
Ну если так, то почему Вы (и никто другой) не указал на эту ошибку сразу же? :)

Да, проверил на примере, освобождает. Все же заметим что старый стандарт дает др формулировку (тот же 5.3.4)
Цитировать
If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function
Тут я явно отстал

Надеюсь, вы осознаете, ...
Ой  :)


Название: Re: Прелести исключений
Отправлено: _Bers от Март 29, 2015, 22:05
Ну если так, то почему Вы (и никто другой) не указал на эту ошибку сразу же? :)

см. моё самое первое вам сообщение.

Да, проверил на примере, освобождает. Все же заметим что старый стандарт дает др формулировку (тот же 5.3.4)
Цитировать
If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function
Тут я явно отстал

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

единственное, что приходит в голову - проблема удаления неполных типов.
но как это состыкуется с new мне не очевидно.

а может быть это как то связанно с наследием прошлого.

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





Название: Re: Прелести исключений
Отправлено: Igors от Март 30, 2015, 10:13
Далеко не первый случай когда поиск бага в исключениями отливается во много часов. Основная трудность - вырубается отладчик. Да, можно найти на каком испускании это случилось (впрочем тоже требует времени). Ну а дальше-то что? Прошагать "Unwind" в asm нереально. Вот с полгода назад был случай

- при испускании исключения "зависает". Краша нет, но все заморожено. Отладчик показывает недра OC, стека вызовов нет. Замена исключения на другое (напр std) - то же самое. Ваши действия? Конечно я спрашиваю не "найти причину", а "как бы Вы искали" (т.е. метод поиска)


Название: Re: Прелести исключений
Отправлено: Пантер от Март 30, 2015, 10:15
Байда с этими исключениями. Я как-то 3 дня потратил, чтобы найти место падения. Пришлось методом перебора по модулям отлавливать. :(


Название: Re: Прелести исключений
Отправлено: _Bers от Март 30, 2015, 14:35
Ваши действия?

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

кроме того, можно отследить всю трассу (наподобие, как в языке java):
поймали в main эксепшен, и через него узнали:
где изначально произошла неполадка, какие и откуда вылетали эксепшены,
кто их ловил, как реагировал, и тп.

очень удобно, особенно если это не тривиальный чужой код.

--------------------------------------------------------------

хотя у меня однажды была проблема:
прилетал обычный int.

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

эксепшен вылетал откуда то из библиотек (там километры километров кода),
которые хз в каких годах писали какие то люди, которые давно уже канули в лету.

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

если сразу делать по уму: никаких проблем, даже если код - чужой.




Название: Re: Прелести исключений
Отправлено: Igors от Март 30, 2015, 16:00
если по уму: глянуть лог.
кто первым зафиксировал неполадки.
кто последний реагировал на неполадки.

кроме того, можно отследить всю трассу (наподобие, как в языке java):
поймали в main эксепшен, и через него узнали:
где изначально произошла неполадка, какие и откуда вылетали эксепшены,
кто их ловил, как реагировал, и тп.

очень удобно, особенно если это не тривиальный чужой код.
Конечно это было бы прекрасно, но откуда ж взять те лог и (особенно) трассу? Если б отладчик показал стек вызовов - так нету!  :'(

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


Название: Re: Прелести исключений
Отправлено: Авварон от Март 30, 2015, 17:05
gdb: catch throw


Название: Re: Прелести исключений
Отправлено: _Bers от Март 30, 2015, 17:19
Конечно это было бы прекрасно, но откуда ж взять те лог и (особенно) трассу? Если б отладчик показал стек вызовов - так нету!  :'(

ну почему у них есть, а у вас нету?
потому что они сделали для себя удобный инструмент,
а вы не сделали.

идешки кстати (ну по крайней мере вижал студия 2013 точно) умеют пасти стек вызовов:
точки, где вылетают эксепшены.


Название: Re: Прелести исключений
Отправлено: Igors от Март 30, 2015, 17:32
ну почему у них есть, а у вас нету?
потому что они сделали для себя удобный инструмент,
а вы не сделали.
Не понял кто "они" и что сделали  ???

идешки кстати (ну по крайней мере вижал студия 2013 точно) умеют пасти стек вызовов:
точки, где вылетают эксепшены.
Моя практика (правда на MSVC 2012 ) этого не подтверждает. Может удастся приспособить "Sudden Terminate", есть такой тул. Заметим что если надо "изыскивать средства/утилиты" - то тем самым признается по меньшей мере "существование проблемы".

gdb: catch throw
Что "catch throw" - это нужно где-то набирать?


Название: Re: Прелести исключений
Отправлено: Авварон от Март 30, 2015, 17:56
Что "catch throw" - это нужно где-то набирать?


Да, набирать:
https://sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html
Ну или в Среаторе в окошке "Команды при подключении"


Название: Re: Прелести исключений
Отправлено: Igors от Март 31, 2015, 19:41
Ладно, попробуем тулзами. Вернул сбойную ситуацию, запустил "Sudden Terminate". Получил стек (первый аттач), привожу "содержательную часть" - трассу после вызова std::runtime_error. Я могу посмотреть код каждого вызова (второй и третий аттач). Хмм... ну и как это осмыслить? Не вижу откуда пришли на третий скриншот

То що це воно дало?  :)


Название: Re: Прелести исключений
Отправлено: Igors от Апрель 01, 2015, 09:27
Да, набирать:
https://sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html
Ну или в Среаторе в окошке "Команды при подключении"
Любовь с командной строкой в очередной раз не сложилась
Цитировать
(gdb) catch throw
Catchpoint 5 (throw)
A syntax error in expression, near `throw'.(gdb) catch throw
(gdb) c
Continuing.
И дальше тот же вылет без всяких остановов. Где же я мог ошибиться набирая catch throw? Можно дать доп опцию (тип исключения) - но мне это не нужно. Какой "syntax error"? В общем, стандартный рез-т когда я начинаю чего-то "набирать"  :)


Название: Re: Прелести исключений
Отправлено: Авварон от Апрель 01, 2015, 11:53
Да, набирать:
https://sourceware.org/gdb/onlinedocs/gdb/Set-Catchpoints.html
Ну или в Среаторе в окошке "Команды при подключении"
Любовь с командной строкой в очередной раз не сложилась
Цитировать
(gdb) catch throw
Catchpoint 5 (throw)
A syntax error in expression, near `throw'.(gdb) catch throw
(gdb) c
Continuing.
И дальше тот же вылет без всяких остановов. Где же я мог ошибиться набирая catch throw? Можно дать доп опцию (тип исключения) - но мне это не нужно. Какой "syntax error"? В общем, стандартный рез-т когда я начинаю чего-то "набирать"  :)

lldb попробуйте, gcc на маке древний как г. мамонта


Название: Re: Прелести исключений
Отправлено: Akon от Апрель 02, 2015, 23:02
Код:
try {
  data.ref = new MyClass(id, type);
}
catch (...) {
 delete data.ref;
 data.ref = 0;
}
Что это за код? Смысл использования исключений т.о?

Если MyClass выбросит исключение, то data.ref не изменится, а память освободится (разумеется, в любых деструкторах исключения не допустимы). Посмотрите в какие ассемблерные инструкции ассемблируется new MyClass(id, type). Неотлов исключений связан с несовместимостью ABI, runtime.

Отладка исключений: MSVC IDE с давних времен, например, выдает что-то типа first chance exception, т.е. отладчик может остановиться сразу, еще до первого обработчика.


Название: Re: Прелести исключений
Отправлено: Igors от Апрель 03, 2015, 09:29
Что это за код? Смысл использования исключений т.о?

Если MyClass выбросит исключение, то data.ref не изменится, а память освободится (разумеется, в любых деструкторах исключения не допустимы). Посмотрите в какие ассемблерные инструкции ассемблируется new MyClass(id, type). Неотлов исключений связан с несовместимостью ABI, runtime.

Отладка исключений: MSVC IDE с давних времен, например, выдает что-то типа first chance exception, т.е. отладчик может остановиться сразу, еще до первого обработчика.
Не думаю что все так уж ясно. То что указатель data.ref может быть установлен еще до окончания конструктора - это жевалось много раз на форумах, и я видел даже опцию компилятора что этому посвящена.

Смотреть код new MyClass - ну а что мы там хотим увидеть? Найти выброс исключения можно и средствами MSVC и руками, напр поставив DebugStr перед исключением в конструкторе. Так что с того? Это не объясняет почему не ловится.

Давно заместил что "несовместимость ABI" - это просто то что "тыкается в нос" :) Почему сотни других (таких же) исключений ловятся как положено, а это нет? В первом же посте я писал про попытку воспроизвести ситуацию со своим классом. Что, ABI уже стало совместимо?  :)

В общем меня разочаровал Ваш ответ - типичный выброс заученных знаний, и ничего более


Название: Re: Прелести исключений
Отправлено: Akon от Апрель 03, 2015, 11:57
Ну так вы и смотрите в корень (читай в ассеблерный код), а разные форумы и т.п. жуйте в 3-ю очередь. Там же все будет как на ладони - установится data.ref или нет, освободится память или нет, как выглядит try/catch фрейм. Поверьте, более ясного источника нет. Вот если вы видите, что что-то не так, тогда ищите контролирующие опции компилятора.

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

Цитировать
В общем меня разочаровал Ваш ответ - типичный выброс заученных знаний, и ничего более
Нет проблем. Вы хотите, чтобы я не давал вам более подобного рода ответы?


Название: Re: Прелести исключений
Отправлено: Авварон от Апрель 03, 2015, 12:05
Не думаю что все так уж ясно. То что указатель data.ref может быть установлен еще до окончания конструктора - это жевалось много раз на форумах, и я видел даже опцию компилятора что этому посвящена.

Смотреть код new MyClass - ну а что мы там хотим увидеть? Найти выброс исключения можно и средствами MSVC и руками, напр поставив DebugStr перед исключением в конструкторе. Так что с того? Это не объясняет почему не ловится.

Давно заместил что "несовместимость ABI" - это просто то что "тыкается в нос" :) Почему сотни других (таких же) исключений ловятся как положено, а это нет? В первом же посте я писал про попытку воспроизвести ситуацию со своим классом. Что, ABI уже стало совместимо?  :)

В общем меня разочаровал Ваш ответ - типичный выброс заученных знаний, и ничего более

Определитесь для начала с платформой. У вас мак? Вы уверены, что исключение вообще вылетает, а не, скажем, приходит сигнал? Вы пробовали lldb? (у меня на маке гдб нету вообще, так что я не могу проверить есть там catch throw или нет)


Название: Re: Прелести исключений
Отправлено: Igors от Апрель 03, 2015, 12:41
Ну так вы и смотрите в корень (читай в ассеблерный код),
Об этом я тоже сказал в первом посте. До выброса исключения - все норм, вот правда почему-то отладчик не останавливается на std::runtime_error. Ну не беда, я могу поставить DebugStr перед ней и остановиться. Дальше надо идти внутрь исключения в asm - ну там не прорваться. Какие еще варианты?

Смотреть класс/ABI, ну класс унаследован от QImage, "свои" только простые POD члены. Первое что сделал - "списал" его дав новое имя (выкинул все кроме конструктора) - с таким все ловится. Здесь тоже "дубль-пусто"

Нет проблем. Вы хотите, чтобы я не давал вам более подобного рода ответы?
Вовсе нет, просто от Вас я ожидал большего

Определитесь для начала с платформой. У вас мак? Вы уверены, что исключение вообще вылетает, а не, скажем, приходит сигнал? Вы пробовали lldb? (у меня на маке гдб нету вообще, так что я не могу проверить есть там catch throw или нет)
Так на lldb этой фишки (catch throw) просто нету. А gdb 2013 года - не новье, но и не такой уж старый.

Баг (сам по себе) давно найден и устранен, но 2 дня коту под хвост. И это не первый такой случай. Вот и хочу на этом примере поизучать чтобы в след раз не сидеть днями.


Название: Re: Прелести исключений
Отправлено: Авварон от Апрель 03, 2015, 12:54
Так на lldb этой фишки (catch throw) просто нету. А gdb 2013 года - не новье, но и не такой уж старый.

Баг (сам по себе) давно найден и устранен, но 2 дня коту под хвост. И это не первый такой случай. Вот и хочу на этом примере поизучать чтобы в след раз не сидеть днями.

И верно, нету - http://stackoverflow.com/questions/8122375/lldb-breakpoint-on-exceptions-equivalent-of-gdbs-catch-throw


Название: Re: Прелести исключений
Отправлено: Igors от Апрель 03, 2015, 17:17
Попробовал с lldb (break set -E c++), да, работает. Но это всего лишь останов на точке испускания исключения. Полезная команда, но не помогает найти почему не пришло в catch