Обратите внимание, что я не хочу решать какие-либо проблемы с моим вопросом - я размышлял о вероятности того, что может произойти, и поэтому мне было интересно что-то:
Что именно происходит, если вы удаляете объект и используете gcc в качестве компилятора?
На прошлой неделе я расследовал аварию, когда состояние гонки приводило к двойному удалению объекта.
Произошел сбой при вызове виртуального деструктора объекта, поскольку указатель на таблицу виртуальных функций уже был перезаписан.
Указатель виртуальной функции перезаписывается при первом удалении?
Если нет, то безопасно ли второе удаление, если в это время не выполняется новое выделение памяти?
Мне интересно, почему проблема, с которой я столкнулся, ранее не распознавалась, и единственное объяснение состоит в том, что либо таблица виртуальных функций перезаписывается сразу при первом удалении, либо при втором удалении не происходит сбой.
(Первое означает, что сбой всегда происходит в одном и том же месте, если происходит «гонка» - второй, обычно ничего не происходит, когда происходит гонка - и только если третий поток перезаписывает удаляемый объект в тем временем возникает проблема.)
Редактировать / Update:
Я сделал тест, следующий код вылетает с ошибкой (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;
}
Если я удалю «virtual» из dtor, программа будет прервана glibc, потому что он обнаружит двойное освобождение.
При использовании «virtual» происходит сбой при выполнении косвенного вызова функции деструктора, поскольку указатель на таблицу виртуальных функций недействителен.
И на amd64, и на i686 указатель указывает на допустимую область памяти (кучу), но значение там недопустимо (счетчик? Это очень низкое значение, например, 0x11 или 0x21), поэтому 'call' (или 'jmp') когда компилятор выполнил возвратную оптимизацию) переходит в недопустимую область.
Программа принимает сигнал SIGSEGV,
Ошибка сегментации.
0x0000000000000021
в ?? () (ГБД)
#
0 0x0000000000000021 в ?? ()
#
1 0x000000000040083e в основном ()
Таким образом, при вышеупомянутых условиях указатель на таблицу виртуальных функций ВСЕГДА перезаписывается при первом удалении, поэтому следующее удаление будет переходить в нирвану, если у класса есть виртуальный деструктор.