简体   繁体   English

std::launder 和严格的别名规则

[英]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_1example_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_2std::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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM