Могу ли я new [], затем привести указатель, а затем безопасно удалить [] со встроенными типами в C ++? - PullRequest
17 голосов
/ 26 января 2010

В моем коде у меня есть следующее:

wchar_t* buffer = new wchar_t[size];
// bonus irrelevant code here
delete[] reinterpret_cast<char*>( buffer );

Все рассматриваемые типы являются встроенными и поэтому имеют тривиальные деструкторы. В VC ++ приведенный выше код работает нормально - new[] просто выделяет память, а delete[] просто освобождает ее.

Это приемлемо в C ++? Это неопределенное поведение?

Ответы [ 7 ]

18 голосов
/ 26 января 2010

Сначала я думал, что это неопределенное поведение.

5.3.5 / 3: «Во втором варианте ( удалить массив ), если динамический тип объекта, который будет удален отличается от своего статического типа, то поведение не определено. 73) .

Сноска 73 гласит: «Это означает, что объект нельзя удалить с помощью указателя типа void*, поскольку нет объектов типа void».

Возможно, объект в вашем примере не имеет динамический тип, поскольку определение «динамический тип» в 1.3.3 упоминает «наиболее производный объект» и определение «наиболее производный объект» «На 1.8 / 4 идет речь об объектах классового типа. Поэтому я продолжал искать:

5.2.10 / 3: "[reinterpret_cast] может или не может создавать представление отличается от первоначального значения "

5.3.5 / 2: «Значение операнда delete должно быть значением указателя который возник из предыдущего массива новое выражение ».

Я не уверен, приводит ли reinterpret_cast к тому же значению указателя, которое было введено, или нет. Возможно, это прояснилось с помощью какого-то другого стандарта, который я еще не нашел. Я бы не назвал этот код «ОК», не найдя что-то, чтобы окончательно утверждать, что если вы повторно интерпретируете указатель, результат будет таким же «значением указателя», как и раньше, так что, передавая его в delete [], вы передаете «значение указателя "из нового [].

5.2.10 / 7: «За исключением того, что приведение [между определенными типами указателей] и вернуться к своему первоначальному типу дает исходное значение указателя, результат такое преобразование указателя не определены».

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

Мы знаем, что на практике мы можем приводить вещи к unsigned char * для проверки их байтов или void * для копирования POD с использованием memcpy, поэтому должны быть гарантированы некоторые приведения для создания псевдонимов. Вы можете подумать, что если ваша реализация создает псевдонимы с определенными приведениями, то вы передаете «то же значение», которое вы получили от new []. Но я все еще не уверен, что этого достаточно для удаления []. Я думаю, что упускаю что-то важное.

9 голосов
/ 26 января 2010

Это неопределенное поведение, потому что delete[] вызывает неправильный деструктор.Однако wchar_t и char - это POD, поэтому у них нет выделенного деструктора, и все, что делает delete[], - это вызывает реализацию кучи, чтобы освободить указатель.Поэтому, скорее всего, сработает, ни один байт не потерян.Но, строго говоря, это все еще не определено.

5 голосов
/ 26 января 2010

По крайней мере, на мой взгляд, у вас есть статический тип (тип указателя), который отличается от динамического типа (реальный тип объекта, на который он указывает). В таком случае применяется второе предложение §5.3.5 / 3:

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

Редактировать: Поскольку вы, очевидно, хотите выделить буфер «сырой» памяти вместо массива объектов, я бы посоветовал использовать ::operator new вместо new[]. В этом случае то, что вы делаете, четко определено, а также дает читателю четкое указание намерения.

4 голосов
/ 26 января 2010

iso14882 раздел 5.2.10.3:

The mapping performed by reinterpret_cast is is implementation defined

iso14882 раздел 5.3.5.2:

The value of the operand of delete[] shall be the pointer value which resulted from a previous array new-expression

Другими словами, его реализация определяет, вызывает ли delete [] неопределенное поведение. Держитесь подальше.

1 голос
/ 27 января 2010

Поскольку wchar_t и char оба являются встроенными типами, будет вызвана правильная функция освобождения (void operator delete(void* ptr)), и деструктора для вызова нет.

Однако стандарт C ++ 03говорит, что результат reinterpret_cast<T1*>(T2*) не определен (раздел 5.2.10.7):

Указатель на объект может быть явно преобразован в указатель на объект другого типа.За исключением того, что преобразование r-значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов и где требования к выравниванию T2 не являются более строгими, чем требования к T1) и обратно к его исходному типу, даетисходное значение указателя, результат такого преобразования указателя не определен.

Из практического POV я не могу представить реализацию, где значение wchar_t* не является допустимым значением char*, поэтому ваш код должен быть в порядке на всехплатформ.Просто не соответствует стандартам ...

0 голосов
/ 26 января 2010

Почему вы используете reinterpret_cast <..>? Если вы пишете что-то на чистом C ++, вам не нужно переосмысливать приведение. В вашем случае вы не выделяете память для объекта. Вы выделяете память для wchar_t. Почему бы не использовать строку вместо массива wchar_t?

0 голосов
/ 26 января 2010

Оператор delete [] внутренне использует цикл некоторой формы для уничтожения элементов вашего массива. Если элементы являются разными объектами, будет использоваться другой деструктор, что потенциально может привести к неопределенному поведению. Так как типы wchar и char - примитивы - это, вероятно, не вызовет нежелательного поведения.

ПРЕДУПРЕЖДЕНИЕ: ЕСЛИ ВЫ ПРОДОЛЖАЕТЕ, ЧИТАЙТЕ, СДЕЛАЙТЕ НА СВОЙ СОБСТВЕННЫЙ РИСК !! ОБЩИЕ ОПИСАНИЯ НЕОПРЕДЕЛЕННОГО ПОВЕДЕНИЯ ВПЕРЕД. ЭТО ТОЛЬКО ДЛЯ ОБРАЗОВАТЕЛЬНЫХ ЦЕЛЕЙ.

Пример 1:

Если бы у вас было два объекта одинакового размера, и все их деструкторы делали обнуление памяти, то, опять же, это, вероятно, не вызвало бы недопустимого поведения.

Пример 2:

Однако, если у вас было два объекта, в которых один тип инкапсулировал один 4-байтовый дескриптор в ресурс, а другой имел два таких элемента, и вы ввели массив более поздних в единственном случае - тогда вы бы потеряли половину ручки вашего массива. Ситуация будет выглядеть следующим образом:

.. 2: [1 | 2] [1 | 2] бесплатно ..

где '2:' представляет размер массива. После сброса компилятор сгенерирует удаление, которое воспринимает данные так:

.. 2: [1] [1] бесплатно ...

поэтому после свободных вещей будет выглядеть так:

.. FREE [1 | 2] бесплатно ..

...