c++ - std::launder 和严格的别名规则

标签 c++ language-lawyer c++17 strict-aliasing placement-new

考虑这段代码:

void f(char * ptr)
{
    auto int_ptr = reinterpret_cast<int*>(ptr); // <---- line of interest 
    // use int_ptr ... 
}

void example_1()
{
    int i = 10;    
    f(reinterpret_cast<char*>(&i));
}

void example_2()
{
    alignas(alignof(int)) char storage[sizeof(int)];
    new (&storage) int;
    f(storage);
}

来自 example_1 的调用的兴趣行:

Q1:在调用方,char 指针是我们整数指针的别名。这是有效的。但是将其转换回 int 是否也有效?我们知道 int 在其生命周期内存在,但考虑到该函数是在另一个翻译单元中定义的(未启用链接时优化)并且上下文未知。然后所有编译器看到的是:int 指针想要别名 char 指针,这违反了严格的别名规则。那么允许吗?

Q2: 考虑到不允许。我们在 C++17 中得到了 std::launder。它是一种指针优化屏障,主要用于访问将 new 放置到其他类型对象的存储中的对象,或者当涉及 const 成员时。我们可以用它来给编译器一个提示并防止未定义的行为吗?

来自 example_2 的调用的兴趣线:

Q3:这里应该需要std::launder,因为这是Q2中描述的std::launder用例,对吧?

auto int_ptr = std::launder(reinterpret_cast<int*>(ptr));

但再考虑一下 f 是在另一个翻译单元中定义的。编译器如何知道发生在调用端的 new 位置?编译器(只看到函数 f)如何区分 example_1example_2?或者以上只是假设,因为严格的别名规则只会排除所有内容(记住,char*int* 不允许)并且编译器可以做它想做的事?

后续问题:

问题 4: 如果以上所有代码都由于别名规则而出错,请考虑将函数 f 更改为采用空指针:

void f(void* ptr)
{
    auto int_ptr = reinterpret_cast<int*>(ptr); 
    // use int_ptr ... 
}

那么我们就没有别名问题了,但是 example_2 仍然存在 std::launder 的情况。我们是否更改了调用端并将我们的 example_2 函数重写为:

void example_2()
{
    alignas(alignof(int)) char storage[sizeof(int)];
    new (&storage) int;
    f(std::launder(storage));
}

或者函数 f 中的 std::launder 是否足够?

最佳答案

严格别名规则是对实际用于访问对象的泛左值类型的限制。对于该规则而言,重要的是 a) 对象的实际类型,以及 b) 用于访问的泛左值的类型。

指针经过的中间转换是无关紧要的,只要它们保留指针值即可。 (这是双向的;就此而言,再多的巧妙转换或洗钱也无法解决严格的混叠违规问题。)

f 有效只要 ptr 实际上指向 int 类型的对象,假设它通过 访问该对象int_ptr 无需进一步转换。

example_1 写的是有效的; reinterpret_cast 不会更改指针值。

example_2 是无效的,因为它为 f 提供了一个指针,该指针实际上并不指向 int 对象(它指向 out- storage 数组的第一个元素)。参见 Is there a (semantic) difference between the return value of placement new and the casted value of its operand?

关于c++ - std::launder 和严格的别名规则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51204362/

相关文章:

c++ - C++ 运算符重载返回中的常量正确性

c++ - 缓存方法结果时如何处理常量?

c++ - 注入(inject) C++ DLL

c++ - 有没有办法创建一个以成员函数或成员作为参数的函数?

c++ - 安装 GCC 7.1 会在标准库中包含 std::is_base_of_v 吗?

c++ - 在 C++ 中,是否允许删除 list<Pointer>::unique 中的对象

C++ 函数作用域静态变量究竟何时初始化?

c++ - 大括号的数量如何影响统一初始化?

c++ - 为什么 C++ 类型表达式不是从左到右解释的?

c++ - 自 c++17 以来,复制省略不需要复制或移动构造函数的存在和可访问性