是否使用 new char[] 或 malloc 的结果来转换 float * is IN(严格的别名违规)?

Is using the result of new char[] or malloc to casted float * is UB (strict aliasing violation)?

其中哪些代码有 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;
}

我问这个,因为 this 问题。

我认为 d 没有 UB(否则 malloc 在 C++ 中将毫无用处)。正因为如此,bce 也没有。我哪里错了吗?也许 b 是 UB,但 c 不是?

尽管是我和 OP 之间的讨论产生了这个问题,但我仍然会把我的解释放在这里。

我相信除了 c() 之外的所有这些都包含标准正式定义的严格的别名违规。

我基于 standard

的第 1.8.1 节

... An object is created by a definition (3.1), by a new-expression (5.3.4) or by the implementation (12.2) when needed. ...

reinterpret_cast<>内存不属于这两种情况。

来自cppreference

Type aliasing

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  • AliasedType and DynamicType are similar.
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
  • AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

Informally, two types are similar if, after stripping away cv-qualifications at every level (but excluding anything inside a function type), they are the same type.

For example: [...some examples...]

还有cppreference:

a glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function;

以上内容适用于除 (c) 以外的所有示例。类型既不是相似的也不是 signed/unsigned 变体。此外,AliasedType(您转换为的类型)既不是 charunsigned char 也不是 std::byte。因此,所有这些(但 c 除外)都表现出未定义的行为。

免责声明: 首先cppreference不是官方参考,只是标准。其次,不幸的是,我什至不能 100% 确定我对 cppreference 上所读内容的解释是否正确。

前言:存储对象在C++中是不同的概念。 存储指的是内存space,对象是有生命周期的实体,可以在一块存储中创建和销毁。随着时间的推移,存储可以重新用于托管多个对象。所有对象都需要存储,但可以有没有对象的存储。


c 是正确的。 Placement-new 是在存储中创建对象的有效方法之一 (C++14 [intro.object]/1),即使该存储中已经存在对象也是如此。旧对象通过存储的重新使用隐式销毁,只要它们没有非平凡的析构函数 ([basic.life]/4),这就完全没问题。 new(a) float; 在现有存储 ([expr.new]/1) 中创建类型为 float 和动态存储持续时间的对象。

de 在当前对象模型规则中由于遗漏而未定义:通过泛左值表达式访问内存的效果仅当该表达式引用一个对象时定义;而不是当表达式指的是不包含对象的存储时。 (注意:请不要对现有定义的明显不足留下非建设性的评论)。

这并不意味着"malloc is useless"; mallocoperator new的作用是获得存储。然后您可以在存储中创建对象并使用这些对象。事实上,这正是标准分配器和 new 表达式的工作方式。

ab 是严格的别名违规:float 类型的泛左值用于访问不兼容类型的对象char。 ([basic.lval]/10)


There is a proposal 这将使所有情况都得到明确定义(除了下面提到的 a 的对齐):根据此提案,使用 *f 在该位置隐式创建该类型的对象,但有一些注意事项。


注意:be不存在对齐问题,因为new-expression和::operator new保证为任何类型分配正确对齐的存储 ([new.delete.single]/1)。

但是在std::vector<char>的情况下,虽然标准规定调用::operator new获取存储,但标准并不要求第一个vector元素放在第一个该存储的字节;例如vector 可以决定在前面分配 3 个额外的字节并将它们用于一些簿记。