Обратите внимание, что я не хочу решать какую-либо проблему с моим вопросом - я думал о вероятностях того, что произойдет, и поэтому кое о чем задавался вопросом:
Что именно произойдет, если вы удалите объект и используете gcc в качестве компилятора?
На прошлой неделе я расследовал сбой, когда состояние гонки привело к двойному удалению объекта.
Сбой произошел при вызове виртуального деструктора объекта, так как указатель на таблицу виртуальных функций уже был перезаписан.
Переписывается ли указатель виртуальной функции при первом удалении?
Если нет, то безопасно ли второе удаление, если за это время не будет произведено новое выделение памяти?
Мне интересно, почему проблема, которая у меня была, не была распознана раньше, и единственное объяснение состоит в том, что либо таблица виртуальных функций перезаписывается сразу во время первого удаления, либо второе удаление не приводит к сбою.
(Первое означает, что сбой всегда происходит в одном и том же месте, если происходит "гонка", а вторая означает, что обычно ничего не происходит, когда происходит гонка, и только если третий поток тем временем перезаписывает объект удаления. возникает проблема.)
Изменить/обновить:
Я сделал тест, следующий код вылетает с 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
в ?? () (ГДБ)
#
0 0x0000000000000021 in ?? ()
#
1 0x000000000040083e в главном ()
Таким образом, при вышеупомянутых условиях указатель на таблицу виртуальных функций ВСЕГДА перезаписывается первым удалением, поэтому следующее удаление перейдет в нирвану, если в классе есть виртуальный деструктор.