【发布时间】:2018-12-14 16:57:34
【问题描述】:
考虑这段代码:
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'ed 放置到其他类型对象的存储中或涉及const 成员时的对象。我们可以用它来给编译器一个提示并防止未定义的行为吗?
来自 example_2 的调用感兴趣的行:
Q3:这里应该需要std::launder,因为这是Q2中描述的std::launder用例,对吧?
auto int_ptr = std::launder(reinterpret_cast<int*>(ptr));
但再次考虑f 是在另一个翻译单元中定义的。编译器如何知道我们的位置new,它发生在调用端?编译器(只看到函数f)如何区分example_1 和example_2?或者以上只是假设,因为严格的别名规则只会排除一切(记住,char* 到 int* 不允许)并且编译器可以做它想做的事?
后续问题:
Q4:如果上面的所有代码都因为别名规则而出错,考虑将函数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足够了吗?
【问题讨论】:
标签: c++ language-lawyer c++17 strict-aliasing placement-new