Russian Qt Forum
Ноябрь 22, 2024, 23:01 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Прелести исключений  (Прочитано 14318 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Март 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 в тестовый пример не удается, он завязан на кучу других, поэтому "код в студию" не получится. Что можно предпринять?

Спасибо
Записан
kamre
Частый гость
***
Offline Offline

Сообщений: 233


Просмотр профиля
« Ответ #1 : Март 28, 2015, 12:56 »

Может быть такое: во время выброса исключения из конструктора MyClass происходит вызов деструкторов для уже созданных его членов, и происходит выброс исключения из деструктора какого-то члена класса MyClass?
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #2 : Март 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;
}

ваш код выполняет неправомерную попытку удаления чужой памяти.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Март 28, 2015, 16:54 »

Может быть такое: во время выброса исключения из конструктора MyClass происходит вызов деструкторов для уже созданных его членов, и происходит выброс исключения из деструктора какого-то члена класса MyClass?
Смотрел, там только POD члены - ну и сам он наследник QImage. А главное - деструктор не вызывается зовется исключением если оно выброшено из конструктора.

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

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

Сообщений: 486


Просмотр профиля
« Ответ #4 : Март 28, 2015, 20:18 »

Ну программист который писал это в 90-х (старый код) конечно занулил указатель еще до try, поэтому здесь утечка (и то не на всех компиляторах).

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

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

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

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Март 29, 2015, 08:27 »

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

Сообщений: 486


Просмотр профиля
« Ответ #6 : Март 29, 2015, 10:10 »

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

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

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

причем тут multi-threadng я вообще не понял.
но однозначно, он не имеет никакого отношения ни к эксепшенам,
ни к операции выделения памяти.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Март 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 до того как конструктор отработал.
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #8 : Март 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, что бы оно смогло выжить после его внезапного завершения.

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

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

Пример

Понял.

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

Не является спецификой операции выделения памяти, и не имеет к этому отношения.
« Последнее редактирование: Март 29, 2015, 11:52 от _Bers » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #9 : Март 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
Тут я явно отстал

Надеюсь, вы осознаете, ...
Ой  Улыбающийся
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #10 : Март 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 мне не очевидно.

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

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



Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Март 30, 2015, 10:13 »

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

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

Сообщений: 5876


Жаждущий знаний


Просмотр профиля WWW
« Ответ #12 : Март 30, 2015, 10:15 »

Байда с этими исключениями. Я как-то 3 дня потратил, чтобы найти место падения. Пришлось методом перебора по модулям отлавливать. Грустный
Записан

1. Qt - Qt Development Frameworks; QT - QuickTime
2. Не используйте в исходниках символы кириллицы!!!
3. Пользуйтесь тегом code при оформлении сообщений.
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #13 : Март 30, 2015, 14:35 »

Ваши действия?

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

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

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

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

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

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

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

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

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


Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Март 30, 2015, 16:00 »

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

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

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

В моем случае я допер что кто-то гадит в деструкторе при раскрутке. Ну сделал свой класс с печатью в деструкторе, натыкал его везде и так (с горем пополам) локализовал. Оказалось деструктор одного из классов повис на мутексе, вот такая милая шутка  Улыбающийся
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.28 секунд. Запросов: 22.