当我使用g ++编译这个示例代码时,我收到此警告:
警告:解除引用类型惩罚指针将破坏严格别名规则
[-Wstrict-aliasing]
代码:
#include <iostream>
int main()
{
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int;
*myInt = 34;
std::cout << *reinterpret_cast<int*>(data);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,不是data别名int,因此将其强制转换为int不会违反严格的别名规则?或者我在这里遗漏了什么?
编辑:奇怪,当我这样定义时data:
alignas(int) char* data = new char[sizeof(int)];
Run Code Online (Sandbox Code Playgroud)
编译器警告消失了.堆栈分配是否与严格别名产生差异?事实上它是一个char[]而不是一个char*意味着它实际上不能为任何类型别名吗?
有一个类似的问题在这里,但在这一问题的用户似乎有一个更大的阵列,或载体。如果我有:
bool boolArray[4];
Run Code Online (Sandbox Code Playgroud)
我想检查所有元素是否为假,我可以分别检查[0],[1],[2]和[3],也可以循环遍历。由于(据我所知)false应该具有值0,而除0以外的其他任何东西都为true,所以我想到了简单地做:
if ( *(int*) boolArray) { }
Run Code Online (Sandbox Code Playgroud)
这行得通,但是我意识到它依赖于布尔值是一个字节,整数是四个字节。如果我强制转换为(std :: uint32_t)可以,还是一个坏主意?我刚好在一个数组中有3或4个布尔值,并且想知道这是否安全,如果不是,那么是否有更好的方法可以做到。
另外,如果我最终得到超过4个布尔值,但少于8个布尔值,那么我可以使用std :: uint64_t或unsigned long long之类的东西做同样的事情吗?
我正在写一个类似于类型擦除的函数包装器std::function.(是的,我已经看过类似的实现,甚至是p0288r0提案,但我的用例非常狭窄且有些专业化.).以下大量简化的代码说明了我当前的实现:
class Func{
alignas(sizeof(void*)) char c[64]; //align to word boundary
struct base{
virtual void operator()() = 0;
virtual ~base(){}
};
template<typename T> struct derived : public base{
derived(T&& t) : callable(std::move(t)) {}
void operator()() override{ callable(); }
T callable;
};
public:
Func() = delete;
Func(const Func&) = delete;
template<typename F> //SFINAE constraints skipped for brevity
Func(F&& f){
static_assert(sizeof(derived<F>) <= sizeof(c), "");
new(c) derived<F>(std::forward<F>(f));
}
void operator () (){
return reinterpret_cast<base*>(c)->operator()(); //Warning
}
~Func(){
reinterpret_cast<base*>(c)->~base(); //Warning
} …Run Code Online (Sandbox Code Playgroud) 我知道显式调用析构函数会因为双析构函数调用而导致未定义的行为,如下所示:
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
return 0; // Oops, destructor will be called again on return, double-free.
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我们将新位置称为"复活"对象,该怎么办?
#include <vector>
int main() {
std::vector<int> foo(10);
foo.~vector<int>();
new (&foo) std::vector<int>(5);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
更正式的:
new)然后,在该对象被破坏之前,在其上调用placement new以"恢复"它?示例用例(虽然这个问题更多是关于好奇心):我想"重新分配"一个没有的对象operator=.
我已经看到这个问题,说"覆盖"具有非静态const成员的对象是非法的.所以,让我们将这个问题的范围限制在没有任何const成员的对象上.
有两个关于替换不可赋值的向量元素的问题:
对象不可分配的典型原因是其类定义包含const成员,因此将其operator=删除.
std::vector要求其元素类型可分配.事实上,至少使用GCC,当对象不可分配时,直接赋值(vec[i] = x;),erase()以及insert()替换元素的组合都不起作用.
可以使用像下面这样的函数来使用vector::data(),直接元素销毁和使用复制构造函数的新元素来替换元素而不会导致未定义的行为吗?
template <typename T>
inline void replace(std::vector<T> &vec, const size_t pos, const T& src)
{
T *p = vec.data() + pos;
p->~T();
new (p) T(src);
}
Run Code Online (Sandbox Code Playgroud)
下面是一个使用函数的例子.这在GCC 4.7中编译并且似乎有效.
struct A
{
const int _i;
A(const int &i):_i(i) {}
};
int main() {
std::vector<A> vec;
A c1(1);
A c2(2);
vec.push_back(c1);
std::cout << vec[0]._i << std::endl;
/* To replace the element …Run Code Online (Sandbox Code Playgroud) 我刚读完
坦率地说,我只能挠头。
让我们从@NicolBolas 接受的答案中的第二个例子开始:
Run Code Online (Sandbox Code Playgroud)aligned_storage<sizeof(int), alignof(int)>::type data; new(&data) int; int *p = std::launder(reinterpret_cast<int*>(&data));[basic.life]/8 告诉我们,如果在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。
std::launder允许我们回避这一点。
那么,为什么不改变语言标准,以便data通过 a访问reinterpret_cast<int*>(&data)是有效/适当的?在现实生活中,洗钱是一种向法律隐瞒现实的方式。但我们没有什么可隐瞒的——我们在这里做的事情完全合法。那么当编译器std::launder()注意到我们正在以data这种方式访问时,为什么不能将其行为更改为它的行为呢?
继续第一个例子:
Run Code Online (Sandbox Code Playgroud)X *p = new (&u.x) X {2};因为 X 是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的 n 成员将为 2。
所以告诉我……什么会
u.x.n回来?显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的
const变量(不仅仅是一个 const&,而是一个声明的对象变量const)永远不会改变。但我们只是改变了它。
那么,为什么不使编译器不能被允许作一个假设,当我们写这样的代码,通过该指针访问恒场?
为什么使用这个伪函数来在形式语言语义中打一个洞是合理的,而不是根据代码是否像这些示例中那样将语义设置为它们需要的样子?
什么情况下,reinterpret_cast荷兰国际集团一char*(或char[N])是不确定的行为,当它定义的行为?我应该用什么经验来回答这个问题?
正如我们从这个问题中学到的,以下是未定义的行为:
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int; // OK
*myInt = 34; // OK
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder
Run Code Online (Sandbox Code Playgroud)
但是在什么时候我们可以reinterpret_cast在一个char数组上做一个并且它不是未定义的行为?以下是一些简单的例子:
不new,只是reinterpret_cast:
alignas(int) char data[sizeof(int)];
*reinterpret_cast<int*>(data) = 42; // is the first cast write UB?
int i = *reinterpret_cast<int*>(data); // how about a read?
*reinterpret_cast<int*>(data) = 4; // how about the second write?
int j …Run Code Online (Sandbox Code Playgroud)c++ undefined-behavior language-lawyer reinterpret-cast c++17
这个问题followes这一个
我们来考虑这个示例代码:
struct sso
{
union{
struct {
char* ptr;
char size_r[8];
} large_str;
char short_str[16];
};
bool is_short_str() const{
return *std::launder(short_str+15)=='\0'; //UB?
}
};
Run Code Online (Sandbox Code Playgroud)
如果short_str不是取消引用指针的活动成员而std::launder不是UB.让我们考虑ABI已经明确指定,并且我们知道size_r [7]与short_str [15]位于同一地址.并std::launder(short_str+15)返回一个指针size_r[7]的时候short_str是不是工会的活跃成员?
Nota:我认为情况就是这样,因为[ptr.launder]/3
如果对象Y位于Y所占用的存储区内,则指向存储的字节可以到达,如果Y是指针可互换的对象,则指向对象Y,或者如果Y是数组元素,则指向立即封闭的数组对象.
当在实例emplace_back()上调用时std::vector,会在先前分配的存储中创建一个对象。这可以通过 Placement-new 轻松实现,它非常便携。但现在,我们需要访问嵌入的元素而不调用未定义的行为。
从这篇文章中 我了解到有两种方法可以做到这一点
使用placement-new返回的指针:
auto *elemPtr = new (bufferPtr) MyType();
或者,从 C++17 开始,std::launder所转换的指针bufferPtr
auto *elemPtr2 = std::launder(reinterpret_cast<MyType*>(bufferPtr));
第二种方法可以很容易地推广到这样的情况,即我们有很多对象放置在相邻的内存位置,如std::vector. 但在 C++17 之前人们做了什么?一种解决方案是将placement-new 返回的指针存储在单独的动态数组中。虽然这当然是合法的,但我认为它并没有真正实现 std::vector [此外,单独存储我们已经知道的所有地址是一个疯狂的想法]。另一个解决方案是存储lastEmplacedElemPtrinside std::vector,并从中删除适当的整数 - 但由于我们实际上没有MyType对象数组,这可能也是未定义的。事实上,此 cppreference 页面中的一个示例声称,如果我们有两个相同类型的指针比较相等,并且其中一个可以安全地取消引用,则取消引用另一个可能仍然是未定义的。
那么,在 C++17 之前有没有办法以可移植的方式实现 std::vector 呢?或者也许 std::launder 确实是 C++ 的一个关键部分,当涉及到新的放置时,自 C++98 以来就缺失了?
我知道这个问题表面上与SO上的许多其他问题相似,但据我所知,他们都没有解释如何合法地迭代由placement-new构造的对象。事实上,这一切都有点令人困惑。例如,示例形式的cppreference 文档中的 std::aligned_storage注释
似乎表明 C++11 和 C++17 之间存在一些变化,并且简单的别名违规reinterpret_cast在 C++17 之前是合法的 [无需std::launder]。类似地,在文档 的示例中,std::malloc
他们只是对返回的指针进行指针算术 …