Базовый указатель на массив производных объектов

Следуя question, заданному здесь ранее сегодня, и множеству аналогичных тематических вопросов, я здесь, чтобы спросить об этой проблеме с точки зрения stadard.

struct Base
{
  int member;
};

struct Derived : Base
{
  int another_member;
};

int main()
{
  Base* p = new Derived[10]; // (1)
  p[1].member = 42; // (2)
  delete[] p; // (3)
}

В соответствии со стандартом (1) хорошо сформирован, потому что Dervied* (который является результатом нового выражения) может быть неявно преобразован в Base* (С++ 11 draft, §4.10/3):

Значение типа "указатель на cv D", где D - тип класса, может быть преобразуется в prvalue типа "указатель на cv B", где B является базой класса (пункт 10) D. Если B является недоступным (пункт 11) или двусмысленный (10.2) базовый класс D, программа, которая требует этого преобразование плохо сформировано. Результатом преобразования является указатель на субобъект базового класса объекта производного класса. Нулевой указатель значение преобразуется в значение нулевого указателя для типа адресата.

(3) приводит к поведению undefined из-за §5.3.5/3:

В первом альтернативе (удалить объект), если статический тип объект, подлежащий удалению, отличается от его динамического типа, статический тип должен быть базовым классом динамического типа объекта, который должен быть и статический тип должен иметь виртуальный деструктор или поведение undefined. Во втором альтернативе (удалить массив), если динамический тип подлежащего удалению объекта отличается от его статического типа, поведение undefined.

Является ли (2) законным в соответствии со стандартом или это приводит к неправильной программе или поведению undefined?

edit: Лучшая формулировка

Ответ 1

Если вы посмотрите на выражение p[1], p - это Base* (Base - полностью определенный тип), а 1 - int, поэтому согласно ISO/IEC 14882: 2003 5.2.1 [expr.sub] это выражение действительно и идентично *((p)+(1)).

Из 5.7 [expr.add]/5, когда целое число добавляется к указателю, результат определяется только тогда, когда указатель указывает на элемент объекта массива, а результат арифметики указателя также указывает на элемент этого объекта массива или один за концом массива. p, однако, не указывает на элемент объекта массива, он указывает на под-объект базового класса объекта Derived. Объектом Derived является элемент массива, а не под-объект Base.

Обратите внимание, что в соответствии с 5.7/4 для целей операции добавления под-объект Base можно рассматривать как массив размера один, поэтому технически вы можете сформировать адрес p + 1, но как "один за последним элементом" указатель, он не указывает на объект Base, и попытка чтения или записи на него приведет к поведению undefined.

Ответ 2

(3) приводит к поведению undefined, но оно не является плохо строгим. Неправильное формирование означает, что программа на С++ не построена в соответствии с правилами синтаксиса, диагностическими семантическими правилами и Правилом определения.

То же самое для (2), оно хорошо сформировано, но оно не делает то, что вы, вероятно, ожидали. В соответствии с разделом 8.3.4/6:

За исключением того, что он был объявлен для класса (13.5.5), индексный оператор [] интерпретируется таким образом что E1 [E2] идентичен * ((E1) + (E2)). Из-за правил преобразования, которые применяются к +, если E1 является массив и E2 - целое число, то E1 [E2] относится к E2-му члену E1. Поэтому, несмотря на свою асимметричную внешний вид, подписка является коммутативной операцией.

Итак, в (2) вы получите адрес, который является результатом p+sizeof(Base)*1, когда вы, вероятно, захотите получить адрес p+sizeof(Derived)*1.

Ответ 3

Стандарт не запрещает (2), но тем не менее он опасен.

Проблема заключается в том, что выполнение p[1] означает добавление sizeof(Base) к базовому адресу p и использование данных в этой ячейке памяти в качестве экземпляра Base. Но вероятность очень велика, что sizeof(Base) меньше sizeof(Derived), поэтому вы будете интерпретировать блок памяти, начиная с середины объекта Derived, как объект Base.

Дополнительная информация в С++ FAQ Lite 21.4.

Ответ 4

p[1].member = 42; 

хорошо сформирован. Статический тип для p равен Derived, а динамический тип Base. p[1] эквивалентен *(p+1), который кажется действительным и является указателем на первый элемент динамического типа Base в массиве.

Однако *(p+1) фактически ссылается на член массива типа Derived. Код p[1].member = 42; показывает, что вы ссылаетесь на элемент массива с типом Base.