【问题标题】:Can std::launder be used to convert an object pointer to its enclosing array pointer?可以使用 std::launder 将对象指针转换为其封闭数组指针吗?
【发布时间】:2019-01-04 06:27:24
【问题描述】:

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

[ 注意:数组对象和它的第一个元素不是指针可互转换的,即使它们具有相同的地址。 — 尾注 ]

所以指向对象的指针不能reinterpret_cast'd 来获取其封闭的数组指针。

现在,有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 指向一个数组的对象,那么根据这个定义,数组的字节是可达的(即使p 不是指针互转换为数组指针),就像洗钱的结果。所以,这个定义似乎没有说明这个问题。

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

【问题讨论】:

  • “可达”这里是一个术语。 “备注:”段落的后半部分只是“需要:”段落中术语“可达”的解释/定义。
  • 在数组的情况下,“可达”指的是简单的指针算法,如p + 1。您可以从任何其他元素到达任何元素。指针互转换是指转换为指针指向整个数组,如int (*) [10],这是一个完全不同的故事。您不需要这种相互转换来访问其他元素。
  • @AnT:感谢您的澄清!

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


【解决方案1】:

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

我想一些例子会有所帮助。下面的每个示例reinterpret_casts 一个int* 指向一个数组的第一个元素,即10 个ints 到一个int(*)[10]。由于它们不是指针可互转换的,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].ax4[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])); 

这又是未定义的,因为您可以从结果指针(通过reinterpret_castY*)访问x5.y,但不能使用源指针访问它。

【讨论】:

  • 那么根据这个推理,表达式的source会以某种方式影响std::launder的语义?不管优化器是如何实现的,这听起来像一个心理模型很疯狂。让我想起了整个指针出处的事情。
  • 也许std::launder 正是针对那些“心智模型” 疯狂的情况;-)
  • std::launder 的规范完全没有提到 reinterpret_cast 的论点,甚至没有提到 reinterpret_cast。它只说明了std::launder 的论点和结果。也就是说,在您考虑的情况下,关于reinterpret_cast结果
  • @n.m.在上述所有示例中,reinterpret_cast 不会更改指针值。它仍然是“指向(无论数组是什么)的第一个元素”。
  • 是的,但它不是“指向整个数组的指针”,尽管具有相同的值。你需要std::launder 才能做到这一点。这就是重点。
【解决方案2】:

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

但是恕我直言,您的问题的答案是否定的。如果 Y 是一个数组元素,则强调的立即封闭的数组对象 位于 Remark 段落中,而不是在 Requires 段落中。这意味着只要遵守要求部分,注释之一也适用。由于数组及其元素类型不是相似类型,所以不满足要求,不能使用std::launder

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

【讨论】:

  • 一个数组和它的第一个元素位于同一个地址,并且数组类型肯定和它自己相似,所以需要的部分确实适用。
  • @n.m.:澄清一下:你的意思是,答案是“是”吗?
  • 是的,我想是的。免责声明:这是在对this question of mine 的答复(由版主删除)中说明的。
  • @n.m. :我已阅读您引用的已删除答案。我不同意它,但恕我直言,这是一个答案而不是评论。所以我想知道它是否没有被错误地删除。你介意我问一下元数据吗?
  • @n.m.:我刚刚问过question 关于meta...
猜你喜欢
  • 2023-02-02
  • 2018-06-12
  • 1970-01-01
  • 2018-08-08
  • 1970-01-01
  • 2019-12-17
  • 2012-05-03
  • 1970-01-01
相关资源
最近更新 更多