Подтвердить что ты не робот

Использует результат нового 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 не?

4b9b3361

Ответ 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, верна.