Удалить буфер через указатель другого типа?

Скажем, у меня есть следующий C++:

char *p = new char[cb];
SOME_STRUCT *pSS = (SOME_STRUCT *) p;
delete pSS;

Это безопасно в соответствии со стандартом C++? Нужно ли отбрасывать назад к char* а затем использовать delete[]? Я знаю, что это будет работать в большинстве компиляторов C++, потому что это обычные данные без деструкторов. Гарантируется ли безопасность?

10 ответов

Решение

Это не гарантировано, чтобы быть в безопасности. Вот соответствующая ссылка в C++ FAQ Lite:

[16.13] Могу ли я бросить [] при удалении массива некоторого встроенного типа (char, int, так далее.)?

http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.13

Нет, это неопределенное поведение - компилятор может правдоподобно делать что-то другое, и, как говорится в статье C++ FAQ, на которую ссылается thudbang, operator delete[] может быть перегружен, чтобы сделать что-то другое operator delete, Иногда это может сойти с рук, но также полезно привыкнуть сопоставлять delete[] и new[] для случаев, когда это невозможно.

Я очень сомневаюсь в этом.

Есть много сомнительных способов освобождения памяти, например, вы можете использовать delete на ваше char массив (а не delete[]) и это, вероятно, будет работать нормально. Я подробно писал об этом (извиняюсь за собственную ссылку, но это проще, чем переписать все это).

Компилятор не столько проблема, сколько платформа. Большинство библиотек будут использовать методы выделения базовой операционной системы, что означает, что один и тот же код может вести себя по-разному на Mac по сравнению с Windows и Linux. Я видел примеры этого, и каждый был сомнительным кодом.

Самый безопасный подход - всегда выделять и освобождать память, используя один и тот же тип данных. Если вы выделяете charЕсли вы вернете их в другой код, вам, возможно, будет лучше предоставить конкретные методы выделения / удаления:

SOME_STRUCT* Allocate()
{
    size_t cb; // Initialised to something
    return (SOME_STRUCT*)(new char[cb]);
}

void Free(SOME_STRUCT* obj)
{
    delete[] (char*)obj;
}

(Перегрузка new а также delete операторы тоже могут быть вариантом, но мне никогда не нравилось это делать.)

Стандарт C++ [5.3.5.2] объявляет:

Если у операнда есть тип класса, операнд преобразуется в тип указателя, вызывая вышеупомянутую функцию преобразования, и преобразованный операнд используется вместо исходного операнда в оставшейся части этого раздела. В любой альтернативе значение операнда удаления может быть нулевым значением указателя. Если это не нулевое значение указателя, в первой альтернативе (объект удаления) значение операнда удаления должно быть указателем на объект, не являющийся массивом, или указателем на подобъект (1.8), представляющий базовый класс такого объекта. объект (пункт 10). Если нет, поведение не определено. Во втором варианте (массив массива) значение операнда удаления должно быть значением указателя, полученным из предыдущего массива new-expression.77) Если нет, поведение не определено. [Примечание: это означает, что синтаксис выражения delete должен соответствовать типу объекта, выделенного new, а не синтаксису выражения new. - end note] [Примечание: указатель на константный тип может быть операндом выражения удаления; нет необходимости отбрасывать константу (5.2.11) выражения указателя, прежде чем он будет использован в качестве операнда выражения удаления. —Конечная записка]

Это очень похожий вопрос на тот, на который я ответил здесь: текст ссылки

Короче говоря, нет, это не безопасно в соответствии со стандартом C++. Если по какой-то причине вам нужен объект SOME_STRUCT, выделенный в области памяти, размер которой отличается от size_of(SOME_STRUCT) (и лучше бы оно было больше!), тогда вам лучше использовать необработанную функцию распределения, такую ​​как global operator new выполнить выделение, а затем создать экземпляр объекта в сырой памяти с размещением new, размещение new будет очень дешево, если тип объекта не имеет конструктора.

void* p = ::operator new( cb );
SOME_STRUCT* pSS = new (p) SOME_STRUCT;

// ...

delete pSS;

Это будет работать большую часть времени. Это всегда должно работать, если SOME_STRUCT является POD-структурой Это также будет работать в других случаях, если SOME_STRUCTконструктор не выкидывает и если SOME_STRUCT не имеет пользовательского оператора удаления. Эта техника также устраняет необходимость в любых бросках.

::operator new а также ::operator delete являются ближайшим эквивалентом C++ malloc а также free и так как они (при отсутствии переопределений классов) называются по мере необходимости new а также delete выражения, которые они могут (с осторожностью!) использовать в сочетании.

Если вы используете malloc/free вместо new/delete, malloc и free не будут заботиться о типе.

Поэтому, если вы используете POD, похожий на C (обычные старые данные, такие как встроенный тип или структура), вы можете использовать malloc для одного типа и освободить другой. обратите внимание, что это плохой стиль, даже если он работает.

Хотя это должно работать, я не думаю, что вы можете гарантировать его безопасность, поскольку SOME_STRUCT не является символом * (если это не просто определение типа).

Кроме того, поскольку вы используете ссылки различных типов, если вы продолжаете использовать доступ *p и память была удалена, вы получите ошибку времени выполнения.

Это небезопасно, и ни один из ответов до сих пор не подчеркнул достаточно безумия этого. Просто не делайте этого, если вы считаете себя настоящим программистом или хотите работать профессиональным программистом в команде. Вы можете только сказать, что ваша структура на данный момент содержит не деструктор, однако вы закладываете неприятную, возможно, компилятор и системную ловушку на будущее. Кроме того, ваш код вряд ли будет работать так, как ожидалось. Самое лучшее, на что вы можете надеяться, это то, что он не потерпит крах. Однако я подозреваю, что вы будете медленно получать утечку памяти, так как выделение массива через new очень часто выделяет дополнительную память в байтах до возвращаемого указателя. Вы не освободите память, о которой думаете. Хорошая процедура выделения памяти должна учитывать это несоответствие, как и такие инструменты, как Lint и т. Д.

Просто не делайте этого и очищайте свой ум от любого мыслительного процесса, который заставил вас даже подумать о такой ерунде.

Это будет работать нормально, если указанная память и указатель, на который вы указываете, являются POD. В этом случае никакой деструктор не будет вызван так или иначе, и распределитель памяти не знает или не заботится о типе, хранящемся в памяти.

Единственный случай, когда все в порядке с типами, отличными от POD, - это если указатель является подтипом указателя (например, вы указываете на автомобиль с транспортным средством *), а деструктор указателя был объявлен виртуальным.

Я изменил код для использования malloc/free. Хотя я знаю, как MSVC реализует new/delete для обычных старых данных (и SOME_STRUCT в данном случае была структурой Win32, так просто C), я просто хотел знать, является ли это переносимым методом.

Это не так, поэтому я буду использовать то, что есть.

Другие вопросы по тегам