Использует результат нового char [] или malloc для casted float * является UB (строгое нарушение псевдонимов)?
Какой из этих кодов имеет UB (в частности, который нарушает правило строгого сглаживания)?
void a() {
std::vector<char> v(sizeof(float));
float *f = reinterpret_cast<float *>(v.data());
*f = 42;
}
void b() {
char *a = new char[sizeof(float)];
float *f = reinterpret_cast<float *>(a);
*f = 42;
}
void c() {
char *a = new char[sizeof(float)];
float *f = new(a) float;
*f = 42;
}
void d() {
char *a = (char*)malloc(sizeof(float));
float *f = reinterpret_cast<float *>(a);
*f = 42;
}
void e() {
char *a = (char*)operator new(sizeof(float));
float *f = reinterpret_cast<float *>(a);
*f = 42;
}
Я спрашиваю об этом из-за этого вопроса.
Я думаю, что d
не имеет UB (иначе malloc
был бы бесполезен в С++). И из-за этого представляется логичным, что b
, c
и e
тоже не имеет этого. Я где-то ошибаюсь? Может быть, b
- UB, но c
не?
Ответы
Ответ 1
Преамбула: хранилище и объекты - это разные концепции на С++. Хранение относится к пространству памяти, а объекты - это объекты со временем жизни, которые могут быть созданы и уничтожены внутри части хранилища. Хранение может быть повторно использовано для размещения нескольких объектов с течением времени. Все объекты требуют хранения, но в нем может быть хранилище без каких-либо объектов.
c верен. Placement-new является одним из допустимых методов создания объекта в хранилище (С++ 14 [intro.object]/1), даже если в нем хранятся ранее существовавшие объекты. Старые объекты неявно уничтожаются при повторном использовании хранилища, и это совершенно нормально, если у них нет нетривиальных деструкторов ([basic.life]/4). new(a) float;
создает объект типа float
и динамическую продолжительность хранения в существующем хранилище ([expr.new]/1).
d и e являются undefined за счет отсутствия правил текущей модели объектной модели: эффект доступа к памяти через выражение glvalue определяется только тогда, когда это выражение относится к объект; а не тогда, когда выражение относится к хранилищу, не содержащему никаких объектов. (Примечание: пожалуйста, не оставляйте неконструктивные комментарии относительно очевидной неадекватности существующих определений).
Это не означает, что "malloc бесполезен"; эффект malloc
и operator new
заключается в получении хранилища. Затем вы можете создавать объекты в хранилище и использовать эти объекты. На самом деле это точно, как работают стандартные распределители и выражение new
.
a и b являются строгими нарушениями псевдонимов: для доступа к объектам несовместимого типа char
используется значение gl float
. ([Basic.lval]/10)
Есть предложение, которое сделает все случаи четкими (кроме выравнивания a ниже): в рамках этого предложения использование *f
неявно создает объект этого типа в местоположении с некоторыми оговорками.
Примечание. В случаях b не существует проблемы с выравниванием e, поскольку для нового выражения и ::operator new
гарантируется правильное выравнивание хранилища для любого типа ( [new.delete.single]/1).
Однако, в случае std::vector<char>
, хотя стандарт указывает, что ::operator new
вызывается для получения хранилища, стандарт не требует, чтобы первый векторный элемент был помещен в первый байт этого хранилища; например вектор может решить выделить 3 дополнительных байта на передней панели и использовать их для некоторого бухгалтерского учета.
Ответ 2
Несмотря на то, что обсуждение между OP и мной, которое породило этот вопрос, я по-прежнему буду интерпретировать здесь.
Я считаю, что все эти save для c()
содержат строгие нарушения псевдонимов, формально определенные стандартом.
Я основываю это на разделе 1.8.1 стандарта
... Объект создается определением (3.1), новым выражением (5.3.4) или путем реализации (12.2), когда это необходимо....
reinterpret_cast<>
память не попадает ни в один из этих случаев.
Ответ 3
От cppreference:
Наложение типов
Всякий раз, когда делается попытка прочитать или изменить сохраненное значение объект типа DynamicType через glvalue типа AliasedType, поведение undefined, если не выполнено одно из следующих утверждений:
- AliasedType и DynamicType похожи.
- AliasedType - это (возможно, cv-квалифицированный) подписанный или неподписанный вариант DynamicType.
- AliasedType - std:: byte (начиная с С++ 17) char или без знака char: это позволяет проверить объектное представление любого объекта как массив байтов.
Неформально два типа аналогичны, если после снятия cv-квалификаций в каждый уровень (но исключая что-либо внутри типа функции), они одинаковы тип.
Например: [... несколько примеров...]
Также cppreference:
glvalue - это выражение, оценка которого определяет тождество объект, бит-поле или функцию;
Вышеприведенное относится ко всем примерам, кроме (c). Типы не являются ни аналогичными, ни подписанными/неподписанными вариантами. Кроме того, AliasedType
(тип, который вы выбрали) не имеет значения char
, unsigned char
или std::byte
. Следовательно, все они (но c) демонстрируют поведение undefined.
Отказ от ответственности:. Прежде всего, cppreference не является официальной ссылкой, но только стандарт. Во-вторых, к сожалению, я даже не уверен на 100%, если моя интерпретация того, что я читаю на cppreference, верна.