这是严格的别名违规吗?任何类型的指针都可以作为 char 指针的别名吗?

Is this strict aliasing violation? Can any type pointer alias a char pointer?

我仍在努力理解什么是允许的,什么是严格别名不允许的。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我在 char* 缓冲区中放置了一个新的不同类型吗?

template <typename T>
struct Foo
{
    struct ControlBlock { unsigned long long numReferences; };
    Foo()
    {
        char* buffer = new char[sizeof(T) + sizeof(ControlBlock)];
        // Construct control block
        new (buffer) ControlBlock{};
        // Construct the T after the control block
        this->ptr = buffer + sizeof(ControlBlock);
        new (this->ptr) T{};
    }
    char* ptr;

    T* get() { 
        // Here I cast the char* to T*.
        // Is this OK because T* can alias char* or because
        // I placement newed a T at char*
        return (T*)ptr;
    }
};

郑重声明,void* 可以作为任何其他类型指针的别名,任何类型指针都可以作为 void* 的别名。 char* 可以作为任何类型指针的别名,但反之亦然吗?假设对齐正确,任何类型都可以别名 char* 吗?那么下面是允许的吗?

char* buffer = (char*)malloc(16);
float* pFloat = buffer;
*pFloat = 6; // Can any type pointer alias a char pointer?
// If the above is illegal, then how about:
new (pFloat) float; // Placement new construct a float at pointer
*pFloat = 7; // What about now?

一旦我将 char* 缓冲区指针分配给新的分配,为了将其用作浮点缓冲区,我是否需要循环遍历并在每个位置放置新的浮点数?如果我一开始没有将分配分配给 char*,而是分配给 float*,我就可以立即将其用作 float 缓冲区,对吗?

Is this strict aliasing violation?

是的。

Can any type pointer alias a char pointer?

没有

可以洗指点:

T* get() { 
    return std::launder(reinterpret_cast<T*>(ptr)); // OK
}

或者,您可以存储新放置的结果:

Foo()
{
    ...
    this->ptr = new (buffer + sizeof(ControlBlock)) T{};
}
T* ptr;

T* get() { 
    return ptr; // OK
}

do I need to loop through and placement new a float at each place

自从提议 P0593R6 被语言 (C++20) 接受以来。在此之前,placement-new 是标准所要求的。您不必自己编写该循环,因为标准库中有相应的函数模板:std::uninitialized_fill_nuninitialized_default_construct_n 等。另外,您可以放心,一个体面的优化器会编译这样的循环归零指令。

constexpr std::size_t N = 4;
float* pFloat = static_cast<float*>(malloc(N * sizeof(float)));

// OK since P0593R6, C++20
pFloat[0] = 6;

// OK prior to P0593R6, C++20 (to the extent it can be OK)
std::uninitialized_default_construct_n(pFloat, N);
pFloat[0] = 7;

// don't forget
free(pFloat);

P.S。不要在 C++ 中使用 std::malloc,除非你需要它来与需要它的 C API 进行交互(即使在 C 中也是很少见的要求)。我还建议不要重复使用 new char[] 缓冲区,因为它对于演示目的来说是不必要的。相反,使用 operator ::new 分配存储而不创建对象(即使是微不足道的对象)。或者甚至更好,因为您已经有了一个模板,让模板的用户提供他们自己的分配器以使您的模板更普遍有用。

严格别名意味着要取消引用 T* ptr,该地址必须有一个 T 对象,显然是活着的。实际上,这意味着您不能天真地在两个不兼容的类型之间进行位转换,而且编译器可以假定没有两个不兼容类型的指针指向同一位置。

例外是 unsigned charcharstd::byte,这意味着您可以重新解释将任何对象指针强制转换为这 3 种类型的指针并取消引用它。

(T*)ptr; 是有效的,因为在 ptr 处存在一个 T 对象。这就是所需要的全部内容,无论您如何获得该指针*,无论它进行了多少次转换。当 T 具有常量成员时还有更多要求,但这必须更多地处理新放置和对象复活 - 如果您有兴趣,请参阅

*即使在没有 const 成员的情况下也很重要,可能不确定 relevant question 。 @eerorika 的答案更正确地建议 std::launder 或从放置新表达式分配。

For the record, a void* can alias any other type pointer, and any type pointer can alias a void*.

这不是真的,void 不是三种允许的类型之一。但我假设你只是误解了“别名”这个词 - 严格的别名只适用于指针被取消引用时,你当然可以自由地将尽可能多的指针指向你想要的任何地方,只要你不取消引用它们。由于无法取消引用 void*,这是一个 moo 点。

解决你的第二个例子

char* buffer = (char*)malloc(16); //OK

// Assigning pointers is always defined the rules only say when
// it is safe to dereference such pointer.
// You are missing a cast here, pointer cannot be casted implicitly in C++, C produces a warning only.
float* pFloat = buffer; 
// -> float* pFloat =reinterpret_cast<float*>(buffer);

// NOT OK, there is no float at `buffer` - violates strict aliasing.
*pFloat = 6;
// Now there is a float
new (pFloat) float;
// Yes, now it is OK.
*pFloat = 7;