这是严格的别名违规吗?任何类型的指针都可以作为 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_n
、uninitialized_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 char
、char
和 std::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;
我仍在努力理解什么是允许的,什么是严格别名不允许的。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我在 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_n
、uninitialized_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 char
、char
和 std::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;