*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

среда, 9 июня 2010 г.

Проблемы с delete[]

Имеем следующий код:

#define T 2

class A {
  public:
    virtual ~A() { 
      p = 0;
    }
    int p;
};

class B: public A {
  int a;
};

int main() {
  A* a = new B[T];
  delete[] a;
  return 0;
}

У меня эта программа однозначно падает с "Segmentation fault" на строке "delete[] a". Проверено на Sun C++ на Солярисе, GCC на Линуксе и на FreeBSD. Вот, например, что происходит на BSD:

Program received signal SIGSEGV, Segmentation fault.
0x08048743 in main () at new_array.cpp:17
17        delete[] a;

Забавно, что под Windows в VS2008 ничего особенного не происходит.

Как я понимаю, что в этой программе принципиально важно, чтобы она падала: деструктор класса "A" должен быть виртуальным, дочерний класс "B" должен быть больше по размеру (тут есть член "a"), константа "Т" должна быть 2 или более (то есть мы должны создавать несколько экземпляров класса "B"), и деструктор класса "A" должен что-нибудь писать в свои члены (тут есть "p = 0;").

Что же тут происходит?

new[] создает массив экземплятор класса "B". Оператор же delete[] получает на вход указатель типа "A*" и начинает вызывать деструкторы элементов. Так как деструктор класса "А" виртуальный, то в ход пускается таблица виртуальных функций. Итак, отработал деструктор для первого элемента a[0]. Далее delete[] хочет получить адрес следующего элемента массиве "a". И для этого (внимание!) адрес следующего он вычисляется так: "a + sizeof(A)" (ему же на вход дали указатель типа "A*"). Но проблема в том, что sizeof(A) < sizeof(B) (это дает член класса B::a), и "a + sizeof(A)" будет указывать не на второй элемент в массиве "a", а куда-то между первым и вторым элементом, так как реальный адрес второго элемента - "a + sizeof(B)". И все бы ничего, но деструктор класс "A" пишет в член "p", тем самым меняя содержимое памяти, а так как для второго элемента адрес вычислен неправильно (его this указывает непонятно куда), то куда реально попадет 0 в присваивании "p = 0;" уже никто не знает, но явно не туда, куда надо. Вот и "Segmentation fault".

Другого объяснения у меня нет.

Если кто знает лучше, поправьте.

P.S. Забавно, что под виндами ничего страшного не происходит.

Update: В комментариях дали точное объяснение из стандарта: C++ 2003 5.3.5:
...In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined. [Note: this means that the syntax of the delete-expression must match the type of the object allocated by new, not the syntax of the new-expression.]

Update 2: Объяснение, почему не глючит в Visual Studio.