Что точно происходит, если Вы удаляете объект? (gcc) (Когда дважды - удаляют катастрофические отказы?)

Обратите внимание на то, что я не хочу решать любую проблему со своим вопросом - я думал о вероятностях вещей произойти и таким образом задавался вопросом о чем-то:

Что точно происходит, если Вы удаляете на объекте и используете gcc в качестве компилятора?

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

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

Указатель виртуальной функции перезаписывается первым, удаляют?

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

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

(Первые средства, что катастрофический отказ всегда происходит на том же местоположении, если "гонка" происходит - вторая, это обычно, ничего не происходит, когда гонка происходит - и только если третий поток перезаписывает удалить объект тем временем, проблема происходит.)


Редактирование/Обновление:

Я сделал тест, следующие катастрофические отказы кода с segfault (gcc 4.4, i686 и amd64):

class M
{
private:
  int* ptr;
public:
  M() {
  ptr = new int[1];
  }
  virtual ~M() {delete ptr;}
};

int main(int argc, char** argv)
{
  M* ptr = new M();
  delete ptr;
  delete ptr;
}

Если я удаляю 'виртуальное' из dtor, программа прерывается glibc, потому что это обнаруживает бездвойное. С 'виртуальным' катастрофический отказ происходит при выполнении косвенного вызова функции к деструктору, потому что указатель на таблицу виртуальной функции недопустим.

И на amd64 и на i686 указатель указывает на допустимый регион памяти ("куча"), но значение, там недопустимо (счетчик? Это очень низко, например, 0x11 или 0x21) так 'вызов' (или 'jmp', когда компилятор сделал оптимизацию возврата), переходы к недопустимому региону.

Программа получила сигнал SIGSEGV,

Отказ сегментации. 0x0000000000000021

в?? () (gdb)

#0 0x0000000000000021 в?? ()

#1 0x000000000040083e в основном ()

Таким образом с вышеупомянутыми условиями, указатель на таблицу виртуальной функции ВСЕГДА перезаписывается первым, удаляют, таким образом, следующие удаляют, перейдет к нирване, если класс будет иметь виртуальный деструктор.

6
задан Brian Tompsett - 汤莱恩 8 July 2015 в 22:24
поделиться

3 ответа

Это очень зависит от реализации самого распределителя памяти, не говоря уже о каких-либо зависящих от приложения сбоях, таких как перезапись v-таблицы какого-либо объекта. Существует множество схем аллокаторов памяти, все они различаются по возможностям и устойчивости к double free(), но все они имеют одно общее свойство: ваше приложение аварийно завершит работу через некоторое время после второго free().

Причина сбоя обычно заключается в том, что распределитель памяти выделяет небольшой объем памяти перед (header) и после (footer) каждого выделенного куска памяти для хранения некоторых специфических деталей реализации. Заголовок обычно определяет размер куска и адрес следующего куска. Нижний колонтитул обычно является указателем на заголовок чанка. Удаление дважды обычно включает в себя проверку того, свободны ли соседние куски. Таким образом, ваша программа завершится, если:

1) указатель на следующий чанк был перезаписан и вторая free() вызывает segfault при попытке доступа к следующему чанку.

2) нижний колонтитул предыдущего куска был изменен и доступ к заголовку предыдущего куска вызывает segfault.

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

6
ответ дан 8 December 2019 в 18:30
поделиться

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

6
ответ дан 8 December 2019 в 18:30
поделиться

Выполнив delete дважды (или даже free ), память может быть уже перераспределена, а повторное выполнение delete может вызвать повреждение памяти. Размер выделенного блока памяти часто указывается непосредственно перед самим блоком памяти.

Если у вас есть производный класс, не вызывайте удаление для производного класса (дочернего). Если он не объявлен виртуальным, то вызывается только деструктор ~ BaseClass () , оставляя любую выделенную память из DerivedClass для сохранения и утечки. Это предполагает, что у DerivedClass есть дополнительная память, выделенная сверх того, что у BaseClass , которая должна быть освобождена.

т.е.

BaseClass* obj_ptr = new DerivedClass;  // Allowed due to polymorphism.
...
delete obj_ptr;  // this will call the destructor ~Parent() and NOT ~Child()
1
ответ дан 8 December 2019 в 18:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: