2012-03-07 1 views
2

У меня есть C++ код, упомянутый ниже:Беспорядок на практике программирования C++ с обработкой исключений

#include<iostream> 
#include<stdexcept> 

const long MAX = 10240000; 

class Widget{ 
     public: 
      Widget(){ 
         ok = new int [1024]; 
         prob = new int [100*MAX]; 
      } 
      ~Widget(){ 
         std::cout<<"\nDtoR of Widget is called\n"; 
         delete ok; ok = NULL; 
         delete prob; prob = NULL; 
      } 
      //keeping below public: Intentionally 
       int* ok; 
       int* prob; 
}; 


void func(){ 
    Widget* pw = NULL; // <<<<--------------- Issue in Here 
    try{ 
     pw = new Widget; 
    } 
    catch(...){ 
       delete pw; 
       std::cout<<"\nIn catch BLOCK\n"; 
       if(pw->ok == NULL){ 
         std::cout<<"\n OK is NULL\n"; 
       } 
       throw; 
    } 
    delete pw; 
} 

int main(){ 
    try{ 
      func(); 
    } 
    catch(std::bad_alloc&){ 
        std::cout<<"\nError allocating memory."<<std::endl; 
    } 

    std::cout<<std::endl; 
    system("pause"); 
    return 0; 
} 

Теперь в функции FUNC(), я вижу два различных поведение в зависимости от того, если я не установлен указатель «PW» до NULL, и если я устанавливаю указатель «pw» на NULL (например, код выше). У меня создалось впечатление, что «хорошая» практика - сначала установить указатель на NULL, а затем инициализировать его. Но когда я инициализирую его до NULL, тогда на выходе просто отображается «В catch BLOCK» и более поздних ошибках приложения. но если я не устанавливаю указатель pw на NULL, тогда я вижу деструктор pw и вызывается после выхода, без каких-либо сбоев приложений.

DtoR из Widget называется

В уловов BLOCK

OK является NULL

Ошибка выделения памяти.

Нажмите любую клавишу, чтобы продолжить. , ,

Мой вопрос в том, почему такая разница в одном случае, когда мы не инициализируем указатель «pw» на NULL, а в другом случае мы устанавливаем его в NULL. Почему деструктор вызывается в одном случае и почему он не вызывался в другом.

Примечание: Целью этого кода является выброс bad_alloc exeption.

ответ

6

Если вы не установили pw - NULL, тогда вы оставите его неинициализированным. Затем, когда оператор new внутри блока «try» генерирует исключение, он никогда не возвращается, и вы попадаете в блок catch. Поскольку new никогда не возвращался, pw все равно не будет инициализирован, и вы пройдете случайный адрес до delete. Это дает вам неопределенное поведение.

+3

Не забудьте упомянуть, что он делает, если (pw-> ok) после удаления – PlasmaHH

+0

благодаря vlad для объяснения этого поведения. PlasmaHH: Я согласен. Виноват. Я просто пропустил это. –

+0

Вы также можете добавить, что нет смысла называть 'delete pw;' в блоке 'catch', так как гарантировано, что память не была выделена, если' pw = new Widget; 'throws. –

4

В свой улов блока, у вас есть:

if(pw->ok == NULL) 

На данный момент, pw является NULL (или мусор, в том случае, если вы не инициализируются его). pw-ok пытается разыграть его, давая неопределенное поведение (авария в этом случае).

Если вы не инициализировали его, тогда сообщение delete pw потерпит крах перед печатью сообщения «поймать»; скорее всего, он будет печатать сообщение «Dtor» перед сбоем, но нет гарантии, поскольку вы твердо находитесь в сфере неопределенного поведения.

Если вы его инициализировали, то delete pw не нужен, но безвреден; удаление нулевого указателя определено, чтобы ничего не делать. Поэтому в этом случае вы не потерпите крах, пока не разыщите его.

В любом случае у вас есть нефиксируемая утечка памяти - первое выделение ok = new int[1024] будет успешным, но вы потеряли единственный указатель на него. Вот почему вы всегда должны управлять динамической памятью с помощью интеллектуальных указателей, контейнеров и других методов RAII.

+0

Спасибо за объяснение. Хотелось бы, чтобы я выбрал два анны. –

0

Вы видите сбой приложения при установке на NULL PW из-за линии

if (pw->ok == NULL) 

Вы разыменования NULL, который является причиной аварии. В другом случае вы удаляете мусор, который даст вам неопределенное поведение.

Кроме того, вы не должны использовать указатель после вызова delete на нем. Это может вызвать все виды странного поведения.

Чтобы объяснить больше того, что происходит. Конструктор Widget генерирует исключение выделения. В этом случае, скорее всего, выделение для ok завершено, но выделение для prob не удалось. Ваш конструктор никогда не заканчивается, утечка памяти, которая была выделена переменной ok. Если вы хотите, чтобы память была очищена, вы должны добавить попытку catch в свой конструктор.

Widget() : ok(NULL), prob(NULL) 
{ 
    try 
    { 
     ok = new int [1024]; 
     prob = new int [100*MAX]; 
    } 
    catch(...) 
    { 
     std::cout << "\nCtor of Widget threw exception\n"; 
     delete [] ok; 
     delete [] prob; 
     throw; 
    } 
} 
+0

Не забудьте 'delete []' вместо просто 'delete'! –

+0

Хороший улов, и я обычно вызываю других разработчиков на этом во время просмотра кода :) – pstrjds

+0

Нужно также перебросить. Я думаю, что лучшая идея состоит в том, чтобы использовать реальный контейнер, такой как 'std :: vector'. 'std :: array' также будет работать в этом случае. –

-1

Это хорошо, чтобы инициализировать pw в NULL, но при удалении его, вы должны сначала проверить, если ПВт не равно нулю. Что-то вроде:

if (pw) delete pw; 

Кроме того, если pw является NULL, вы не можете ссылаться на его членов.

+0

Однако, выглядя лучше в вашем коде, 'delete pw' внутри' catch' кажется нецелесообразным, поскольку если ваши программы запускают этот код, у вас будет 'NULL'' pw' – f00l

+3

Неправильно. Вы можете безопасно удалить NULL. Это не-op. –

+1

Я должен упомянуть, однако, что ваш второй пункт находится на месте. –

0
  1. Зачем вам называть pw->ok после того, как вы удалите pw? Это уже исчезло.
  2. Вы конструктор должен иметь элемент инициализирует

Widget():ok(NULL), prob(NULL) { 
... 
} 

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

  1. Поскольку вы наделенный int[], вам нужно delete[] не только delete в вашем деструкторе.
+0

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

1

вы намерены использовать bad_alloc exeption.but, у вас есть более необработанные исключения! Неверно удалять pw сначала, а затем с помощью указателя!

  delete pw; 
      if(pw->ok == NULL) 
Смежные вопросы