2012-02-17 4 views
2

У меня есть небольшая программа на C++, где я создаю два объекта класса Person. Этот класс имеет char *m_szFirstName и char *m_szLastName для данных.Удаление памяти, когда она была удалена до

Затем я назначаю один объект другому, заставляя элементы данных объекта указывать на одно и то же местоположение.
В деструкторе я удаляю память, которую я выделил для первого объекта, и назначил значения NULL указателям. Что-то вроде этого.

if (m_szFirstName!= NULL) 
{ 
    delete [] m_szFirstName; 
    m_szFirstName = NULL; 
} 

Затем, когда я иду, чтобы удалить память для второго объекта, проверка на NULL не работает, и когда я удалить память, я получаю аварию. Из отладчика он показывает, что мой указатель не NULL. Он имеет 0xfeee.

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

+0

Можете ли вы показать код? – cnicutar

+0

Когда вы копируете экземпляр 'Person', у второго объекта есть ** его собственные ** члены. То есть он имеет свой собственный 'm_szFirstName', значение которого было скопировано из исходного экземпляра. Обратите внимание, что копируется только указатель, ** не указанная память **. Поэтому, когда вы удаляете первый экземпляр «NULL», второй сохраняет свое значение и указывает на «удаленную» память. – ereOn

+0

код слишком большой, чтобы прикрепить сюда. Я хочу продемонстрировать, что вы не можете передать один объект (с данными char *) другому, если вы не переопределяете оператор присваивания и не делаете в нем копию. В противном случае данные двух объектов будут указывать на одну и ту же память. Когда первый объект будет удален, данные второго объекта будут повреждены. –

ответ

4

причина аварии:
Вы должны следовать Rule of Three, чтобы избежать этой проблемы оборванных указателей.

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

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

Предлагаемое решение:

Если вы можете использовать std::string вместо char * просто использовать std::string, он имеет в первую очередь предпочтение перед какой-либо глупой вещи указателя.
Вы можете избежать всех фанк-указателей, используя std::string.

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

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

Чтобы сделать это, Вы должны использовать Smart pointer, который будет управлять динамической памяти указателя implicitly.Using смарт-указатель будет гарантировать, что динамическая память неявно освобожден после использования & вам не придется вручную управлять ,

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

Оговорка:
Обратите внимание, что я сдержался от предложения который умный указатель использовать, потому что выбор зависит скорее от собственности и жизни элементов, участвующих, что не видно из данных при условии, в Question.So я предлагаю читать,

Which kind of pointer do I use when?

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

+0

Примечание: Когда используется RAII, вы все равно должны следовать [Law Of The Big Two] http://www.artima.com/cppsource/bigtwo.html – Arafangion

+0

Хороший ответ. Однако в его случае очень вероятно, что то, что ему действительно нужно, является 'std :: string' для хранения своих первых имен и последних имен. – ereOn

+0

@ereOn: * Вероятно * Да, * Конечно * Мы не знаем. В любом случае ответ вообще хорош для любых членов-указателей как таковых. Конечно, если можно использовать 'std :: string', он имеет первое и главное предпочтение над любым видом глупых указателей. Я вижу, что многие связанные с назначением предложения Q нажимают на решения, не использующие 'std :: string', которые являются глупыми, я предполагаю, что это один из этих случаев. В любом случае я отредактирую ответ, чтобы отразить это. Спасибо. –

0

Не удаляйте его снова, если (m_szFirstName == m_szLastName). Но это даст вам утечку памяти (когда вы назначаете один указатель на другой).

1

С

if (m_szFirstName!= NULL) 
{ 
    delete [] m_szFirstName; 
    m_szFirstName = NULL; 
} 

Вы только установить m_szFirstName, чтобы указать на NULL, а не m_szLastName. Это означает, что вы должны иметь какой-то способ отслеживать тот факт, что они указывают на то же место. Есть ли причина, по которой они указывают на одно и то же место? Не могли бы вы скопировать имя вместо того, чтобы указывать указатели на одно и то же место?

Если вам нужны два указателя для совместного использования одних и тех же данных, я бы посмотрел на std :: tr1 :: shared_ptr, которые разрешат вам эту проблему, отслеживая количество ссылок и удаляя их, когда число от ссылок достигает 0.

0

Если у вас есть два указателя, указывающих на одно и то же место (после того, как вы назначили первый для второго), у вас есть два указателя, указывающих на один и тот же адрес. Удаление одного освобождает память, на которую указывают оба из них. Но установка одного на NULL не изменяет другого указателя. То же самое происходит, если у вас есть два целых числа.

int a = 3; 
int b = a; 

Теперь, если вы запустите

a = 0; 

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

0

Ваша проблема - это классическая проблема C/C++, известная как проблема «обманывающий указатель». Разыменование висячего указателя привело к сбою. Проблема заключается в подсчете ссылок. После того, как вы назначили одну и ту же память второму указателю, счетчик ссылок должен быть равен 2. Поэтому, если вы удаляете один указатель, счетчик ссылок должен стать 1, и ваша память не должна освобождаться или освобождаться, если только счет равен 0. В 0 может быть собрано мусор ,

Теперь у вас есть хорошие ответы, чтобы решить вашу проблему. Поскольку вы используете C++, вы можете использовать что-то вроде auto_ptr (OR shared_ptr). Они обеспечивают то, что я упомянул выше, материал для подсчета ссылок, и даже вам не нужно беспокоиться об удалении или освобождении ваших объектов, эти классы будут заботиться. Они работают над симуляцией, известной как шаблон RAII, где деструктор автоматически вызван магией, когда объект в стеке выходит за рамки.

0

Просто прекратите указывать указатели до NULL, когда вы удалили объект. Как вы можете видеть, это просто приводит к боли. Вы не можете предположить, что, поскольку указатель не является NULL, он еще не был удален.

Вы можете использовать любой разумный образец, который хотите избежать этой проблемы. Например, Boost's shared_ptr - отличный выбор.

+0

Установка указателя на nullptr или NULL - это правильная вещь после удаления, так что если вы снова удалите один и тот же указатель, ничего не должно произойти. В моем случае, по какой-то глупой причине, он падает. В любом случае, спасибо за ваше предложение. –

+0

@SaleemYusuf Это не помешает вам вызвать сбой, если вы снова удалите тот же указатель, как вы видели. Все, что он делает, заставляет вас думать, что если переменная указателя содержит значение, отличное от NULL, это означает, что он безопасен для доступа, что не является ** true. Так что это очень плохая привычка. Вместо того, чтобы указывать указатель на NULL, просто не обращайтесь к нему. (Или, еще лучше, используйте разумный класс указателей.) –