std ::洗钱和严格的别名规则

时间:2018-07-06 06:38:36

标签: 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的呼叫)

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

第二季度:考虑到不允许。我们在C ++ 17中得到了std :: launder。它是一种指针优化屏障,主要用于访问对象,该对象已在其他类型的对象的存储中或在涉及const成员时进行了新的放置。我们可以使用它为编译器提供提示并防止未定义的行为吗?

感兴趣的线路(来自example_2的呼叫)

第3季度:在这里应该需要std :: launder,因为这是第2季度中描述的std :: launder用例,对吧?

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

但是再考虑一下f是在另一个翻译单元中定义的。编译器如何知道我们在调用端发生的新放置?编译器(仅看到函数f)如何区分example_1和example_2?还是上述所有都是假设性的,因为严格的别名规则只会排除所有内容(记住char *到int *不允许),并且编译器可以执行他想要的操作?

后续问题:

问题4:如果由于别名规则导致上述所有代码都不正确,请考虑更改函数f以使用空指针:

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

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

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

或者功能std::launder中的f是否足够?

2 个答案:

答案 0 :(得分:12)

严格的别名规则是对实际用于访问对象的glvalue类型的限制。对于该规则而言,所有重要的事情是:a)对象的实际类型,以及b)用于访问的glvalue的类型。

只要指针保留指针值,中间经过的转换就无关紧要。 (这是双向的;就此而言,进行任何巧妙的强制转换或洗钱都无法解决严格的混叠违规问题。)

只要f实际上指向类型为ptr的对象,

int就有效,假设它是通过int_ptr访问该对象而无需进一步转换的。

example_1按书面规定有效; reinterpret_cast不会更改指针值。

example_2无效,因为它为f提供了一个指针,该指针实际上并未指向int对象(它指向{{ 1}}数组)。参见Is there a (semantic) difference between the return value of placement new and the casted value of its operand?

答案 1 :(得分:0)

在函数 f() 中不需要 std::launder。尽可能通用,函数 f() 可以与任何指针一起使用:脏(需要清洗)或不。它只在呼叫方知道,所以你必须使用这样的东西:

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)
}

而 f() 本身又是另一回事。正如您提到的,请考虑将 f() 放在共享库中,因此根本没有任何上下文假设。

相关问题