[英]std::launder and strict aliasing rule
Consider this code:考虑这个代码:
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);
}
Line of interest with call from example_1
:来自
example_1
调用的兴趣线:
Q1: On the callside the char
pointer is aliasing our integer pointer. Q1:在调用方,
char
指针是我们的整数指针的别名。 This is valid.这是有效的。 But is it also valid to just cast it back to an
int
?但是将它转换回
int
也有效吗? We know an int
is within its lifetime there, but consider the function is defined in another translation unit (with no linktime optimization enabled) and the context is not known.我们知道一个
int
在它的生命周期内,但考虑到这个函数是在另一个翻译单元中定义的(没有启用链接时优化)并且上下文是未知的。 Then all the compiler sees is: an int
pointer wants to alias a char
pointer, and this is violating the strict aliasing rules.然后编译器看到的是:一个
int
指针想要给一个char
指针取别名,这违反了严格的别名规则。 So is it allowed?那么允许吗?
Q2: Considering it's not allowed. Q2:考虑到这是不允许的。 We got
std::launder
in C++17.我们在 C++17 中得到了
std::launder
。 It's kind of a pointer optimization barrier mostly used to access an object which got placement new
'ed into the storage of an object of other type or when const
members are involved.这是一种主要用于访问的对象,安置了一个指针优化屏障
new
“编辑成其他类型或当一个对象的存储const
成员参与。 Can we use it to give the compiler a hint and prevent undefined behavior?我们可以用它来给编译器一个提示并防止未定义的行为吗?
line of interest with call from example_2:来自example_2的调用的兴趣线:
Q3: Here std::launder
should be required, since this is the std::launder
use case described in Q2, right? Q3:这里应该需要
std::launder
,因为这是 Q2 中描述的std::launder
用例,对吗?
auto int_ptr = std::launder(reinterpret_cast<int*>(ptr));
But consider again f
is defined in another translation unit.但是再次考虑
f
是在另一个翻译单元中定义的。 How can the compiler know about our placement new
, which happens on the callside?编译器如何知道我们的布局
new
,它发生在调用方? How can the compiler (only seeing function f
) distinguish between example_1
and example_2
?编译器(只看到函数
f
)如何区分example_1
和example_2
? Or is all above just hypothetical, since the strict aliasing rule would just rule out everything (remember, char*
to int*
not allowed) and the compiler can do what it wants?或者以上只是假设,因为严格的别名规则只会排除一切(记住,不允许使用
char*
到int*
)并且编译器可以做它想做的事?
Follow-up question:后续问题:
Q4: If all code above is wrong due to aliasing rules, consider changing the function f
to take a void pointer: Q4:如果由于别名规则,以上所有代码都是错误的,请考虑更改函数
f
以采用空指针:
void f(void* ptr)
{
auto int_ptr = reinterpret_cast<int*>(ptr);
// use int_ptr ...
}
Then we have no aliasing problem, but still there is the std::launder
case for example_2
.然后我们没有别名问题,但仍然存在
example_2
的std::launder
情况。 Do we have change the callside and rewrite our example_2
function to:我们是否更改了调用方并将我们的
example_2
函数重写为:
void example_2()
{
alignas(alignof(int)) char storage[sizeof(int)];
new (&storage) int;
f(std::launder(storage));
}
or is std::launder
in function f
sufficient?或者函数
f
std::launder
足够?
The strict aliasing rule is a restriction on the type of the glvalue actually used to access an object.严格别名规则是对实际用于访问对象的泛左值类型的限制。 All that matters for the purpose of that rule are a) the actual type of the object, and b) the type of the glvalue used for the access.
对于该规则而言,所有重要的是 a) 对象的实际类型,以及 b) 用于访问的泛左值的类型。
The intermediate casts the pointer travels through are irrelevant, as long as they preserve the pointer value.指针经过的中间转换是无关紧要的,只要它们保留指针值即可。 (This goes both ways; no amount of clever casts - or laundering, for that matter - will cure a strict aliasing violation.)
(这是双向的;就这一点而言,再多的巧妙转换——或洗钱——都无法解决严格的别名违规问题。)
f
is valid as long as ptr
actually points to an object of type int
, assuming that it accesses that object via int_ptr
without further casting.只要
ptr
实际上指向int
类型的对象, f
就是有效的,假设它通过int_ptr
访问该对象而无需进一步转换。
example_1
is valid as written; example_1
是有效的; the reinterpret_cast
s do not change the pointer value. reinterpret_cast
不会改变指针值。
example_2
is invalid because it gives f
a pointer that doesn't actually point to an int
object (it points to the out-of-lifetime first element of the storage
array). example_2
是无效的,因为它给f
一个实际上并不指向int
对象的指针(它指向storage
数组的生命周期外的第一个元素)。 See Is there a (semantic) difference between the return value of placement new and the casted value of its operand?请参阅放置 new 的返回值与其操作数的强制转换值之间是否存在(语义)差异?
You need not std::launder within the function f().您不需要在函数 f() 中使用 std::launder。 Being as universal as possible, the function f() might be used with any pointer: dirty (that needs to be launded) or not.
尽可能通用,函数 f() 可以与任何指针一起使用:脏(需要清洗)或不。 It's known only on a call side, so you must use something like this:
它只在呼叫方知道,所以你必须使用这样的东西:
void example_2()
{
alignas(alignof(int)) char storage[sizeof(int)];
new (&storage) int; // char[] has gone, now int there
f(std::launder(reinterpret_cast<int*>(storage))); // assiming f(void* p)
}
And the f() itself is another story.而 f() 本身又是另一回事。 As you mentioned, consider f() is placed in a shared library, so there are no any context assumptions at all.
正如您所提到的,请考虑将 f() 放在共享库中,因此根本没有任何上下文假设。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.