可以使用std :: launder将对象指针转换为其封闭的数组指针吗?

时间:2018-07-27 07:17:35

标签: c++ language-lawyer c++17

当前的标准草案(大概是C ++ 17)在[basic.compound/4]中说:

  

[注意:数组对象及其第一个元素即使指针具有相同的地址,也不能指针可互换。 —尾注]

因此无法reinterpret_cast指向对象的指针来获取其封闭的数组指针。

现在,有std::launder[ptr.launder/1]

  

template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;

     

要求:p代表内存中字节的地址A。在其生命周期之内且类型类似于T的对象X位于地址A。通过结果可以访问的所有存储字节都可以通过p进行访问(见下文)。

reachable 的定义在[ptr.launder/3]中:

  

备注:每当在核心常量表达式中使用其参数的值时,就可以在核心常量表达式中使用此函数的调用。通过一个指向对象Y的指针值可以到达一个存储字节,如果该对象位于Y所占用的存储空间内,则该对象可以与Y进行指针互换,如果Y是一个立即封闭数组对象,数组元素。如果T是函数类型或cv void,则程序格式错误。

现在,乍一看,由于我已经强调了这一部分,看来std::launder可以用来进行上述转换。

但是。如果p指向数组的对象,则根据此定义,数组的字节是 reachable (即使p不能指针转换为array-pointer) ,就像洗钱的结果一样。因此,似乎该定义并未说明此问题。

因此,std::launder可以用于将对象指针转换为其封闭的数组指针吗?

2 个答案:

答案 0 :(得分:7)

这取决于封闭的数组对象是否为完整的对象,如果不是,则取决于是否可以通过指向该封闭的数组对象的指针有效访问更多字节(例如,因为它本身是数组元素,或者与指针可相互转换)较大的对象,或指针可与数组元素的对象互换使用)。 “可达”要求意味着,如果出现不确定的行为,则无法使用launder获取一个指针,该指针将允许您访问比源指针值允许的更多字节。这样可以确保某些未知代码可能调用launder的可能性不会影响编译器的转义分析。

我想一些例子可能会有所帮助。 reinterpret_cast下的每个示例均指向一个int*,指向一个int中的10 int(*)[10] s数组的第一个元素。由于它们不是指针可互换的,因此reinterpret_cast不会更改指针值,并且您会得到一个int(*)[10],其值为“指向(无论数组是什么)第一个元素的指针”。然后,每个示例都通过在强制转换指针上调用std::launder来尝试获取指向整个数组的指针。

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); 

这没关系;您可以通过源指针访问x的所有元素,而launder的结果不允许您访问其他任何内容。

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 

这是未定义的。您只能通过源指针访问x2[0]的元素,但是结果(将是指向x2[0]的指针)将允许您访问x2 [1],而您不能通过来源。

struct X { int a[10]; } x3, x4[2]; // assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK

这可以。同样,您无法通过指向x3.a的指针访问尚未访问的任何字节。

auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); 

(尚未定义)。您将能够从结果中获得x4[1],因为x4[0].a可与x4[0]进行指针互换,因此指向前者的指针可以为reinterpret_cast来产生指向后者可用于指针运算。参见https://wg21.link/LWG2859

struct Y { int a[10]; double y; } x5;
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); 

这又是未定义的,因为您可以从结果指针(由x5.yreinterpret_cast)到达Y*,但是不能使用源指针来访问它。

答案 1 :(得分:2)

备注:任何非精神分裂症的编译器都可能会很乐意接受这一点,因为它会接受C样式的强制转换或重新解释的强制转换,因此请尝试看看不是一个选择。

但是恕我直言,您的问题的答案是否定的。如果Y是数组元素,则强调的立即包围数组对象位于备注段落中,而不位于 Requires 中。这意味着只要遵守了require节,则备注也适用。由于数组及其元素类型不是相似的类型,因此无法满足要求,因此无法使用std::launder

接下来是更多的一般(哲学上的)解释。在K&R C时代(70年代),C旨在能够代替汇编语言。因此,规则是:只要源代码可以翻译,编译器就必须服从程序员。因此,没有严格的别名规则,指针也仅仅是具有其他算术规则的地址。这在C99和C ++ 03中发生了重大变化(不涉及C ++ 11 +)。现在,程序员应该使用C ++作为高级语言。这意味着指针只是允许访问给定类型的另一个对象的对象,而数组及其元素类型是完全不同的类型。现在,内存地址只不过是实现细节。因此,尝试将指向数组的指针转换为指向其第一个元素的指针将违反该语言的原理,并可能在更高版本的编译器中对程序员造成不利影响。当然,出于兼容性原因,现实生活中的编译器仍会接受它,但是我们甚至不应该尝试在现代程序中使用它。