作者:GXtingker | 来源:互联网 | 2023-10-11 13:31
我仍在努力理解严格别名允许和不允许的内容。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我将新的不同类型放入char*缓冲区吗?templa
我仍在努力理解严格别名允许和不允许的内容。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我将新的不同类型放入 char* 缓冲区吗?
template
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*,我就可以立即将其用作浮点缓冲区,对吗?
回答
这是严格的别名违规吗?
是的。
任何类型指针都可以作为字符指针的别名吗?
不。
您可以清洗指针:
T* get() {
return std::launder(reinterpret_cast(ptr)); // OK
}
或者,您可以存储新的展示位置结果:
Foo()
{
...
this->ptr = new (buffer + sizeof(ControlBlock)) T{};
}
T* ptr;
T* get() {
return ptr; // OK
}
我是否需要循环遍历并在每个地方放置一个新的浮点数
自从提案P0593R6被该语言接受后就没有了(C++20)。在此之前,标准要求放置新。你不一定要自己写循环,因为有标准库对于函数模板:std::uninitialized_fill_n
,uninitialized_default_construct_n
等等。另外,你可以放心,一个体面的优化器将编译这样的循环,零个指令。
constexpr std::size_t N = 4;
float* pFloat = static_cast(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);
PS 不要std::malloc
在 C++ 中使用,除非您需要它来与需要它的 C API 交互(即使在 C 中这也是很少见的要求)。我还建议不要重用new char[]
缓冲区,因为它对于演示目的来说是不必要的。相反,使用operator ::new
which 分配存储而不创建对象(即使是微不足道的对象)。或者甚至更好,因为您已经有一个模板,让模板的用户提供他们自己的分配器,使您的模板更普遍有用。
回答
严格别名意味着要取消引用 a T* ptr
,T
该地址必须有一个对象,显然是活着的。实际上,这意味着您不能在两个不兼容的类型之间进行天真位转换,并且编译器可以假设没有两个不兼容类型的指针指向同一位置。
例外是unsigned char
,char
和std::byte
,这意味着您可以将任何对象指针重新解释为这 3 种类型的指针并取消引用它。
(T*)ptr;
是有效的,因为ptr
那里存在一个T
对象。这就是所需要的全部内容,通过它进行了多少次转换,您如何获得该指针*并不重要。当T
具有常量成员时还有更多要求,但这与新放置和对象复活有关 -如果您有兴趣,请参阅此答案。
*即使在没有 const 成员的情况下也很重要,可能不确定相关问题。@eerorika 的答案对于建议std::launder
或从放置新表达式中分配更正确。
就记录而言,void* 可以为任何其他类型指针取别名,任何类型指针都可以为 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(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;