为什么使用 `std::aligned_storage` 据称会因无法“提供存储”而导致 UB?

Hol*_*Cat 19 c++ language-lawyer c++23

灵感来源:为什么 std::aligned_storage 在 C++23 中被弃用以及使用什么替代?

\n

链接的提案P1413R3(不赞成使用std::aligned_storage)表示:

\n
\n

使用aligned_*调用未定义的行为(类型无法提供存储。)

\n
\n

这是指[intro.object]/3

\n
\n

如果在与N \xe2\x80\x9d 类型的 \xe2\x80\x9carray或N \xe2 \ 类型 \xe2\x80\x9carray 的另一个对象e关联的存储中创建了完整对象 ([expr.new]) x80\x9d ([cstddef.syn]),该数组为创建的对象提供存储,如果: ... unsigned char std\xe2\x80\x8b::\xe2\x80\x8bbyte

\n
\n

然后,该标准在一些定义中继续使用术语“提供存储”,但我没有看到它在任何地方说使用不同类型作为新放置的存储(无法“提供存储”)会导致 UB 。

\n

那么,问题是:std::aligned_storage当用于放置新时,是什么导致了UB?

\n

Hol*_*Cat 11

该论文在这一点上似乎是错误的。

\n

如果 std::aligned_storage_t未能“提供存储”,那么它的大多数使用都会间接导致UB(见下文)。

\n

但是否std::aligned_storage_t真的能够“提供存储”似乎还没有明确。使用带有成员的结构alignas(Y) unsigned char arr[X];(似乎)的常见实现确实根据以下内容“提供存储”[intro.object]/3,即使您将整个结构的地址传递到placement-new,而不是数组。尽管现在尚未强制执行此特定实施,但我相信强制执行它将是一个简单的非破坏性更改。

\n
\n

如果std::aligned_storage_t实际上没有“提供存储”,那么大多数用例都会导致 UB:

\n

将 new 放置到无法“提供存储”的对象中本身是合法的,但是......

\n

这将结束未能“提供存储”( ) 的对象的生命周期aligned_storage_t,并递归地结束所有封闭对象的生命周期。下次您访问其中任何一个时,您将获得 UB。

\n

即使aligned_storage_t没有嵌套在其他对象中(这种情况很少见),在销毁它时也必须小心,因为调用它的析构函数也会导致 UB,因为它的生命周期已经结束。

\n
\n

[basic.life]/1.5

\n

...类型 T 的对象 o 的生命周期在以下情况结束:

\n

\xe2\x80\x94 对象占用的存储空间...被未嵌套在[对象]内的对象重用

\n
\n
\n

intro.object/4

\n

如果满足以下条件,则对象 a 嵌套在另一个对象 b 中:

\n

\xe2\x80\x94a 是 b 的子对象,或者

\n

\xe2\x80\x94 b 为 a 提供存储,或者

\n

\xe2\x80\x94 存在一个对象 c,其中 a 嵌套在 c 中,c 嵌套在 b 中。

\n
\n

  • 如果该项目符号意味着某人可能会忘记在“std::aligned_storage”之后使用“_t”/“::type”,那么这只是胡说八道^W (3认同)
  • @LanguageLawyer 他们提到分别忘记`_t`/`::type`,所以这确实有点废话。我试图修复答案... (2认同)

Yak*_*ont 7

C++ 标准允许一组非常有限的类型作为其他对象的存储。可以用作其他对象存储的类型集本身不能将对齐方式打包到其类型中。

想象:

template<std::size_t N>
using bytes=std::byte[N];
template<std::size_t S, std::size_t A>
struct alignas(A) aligned{
  bytes<S> data;
};
Run Code Online (Sandbox Code Playgroud)

您不能用来&aligned<12,4>安全地存储另一个对象。您不能使用此属性创建一个与其对齐的 typedef。

您可以使用aligned<12,4> a; &a.dataor 类似的,但这在语法上是不同的。

现在,该标准可以通过添加措辞来绕过它;但是对齐存储的现有定义没有这种神奇的措辞,并且在没有这种aligned_storage_t措辞的情况下,C++ 中的任何构造都不能具有用户期望的属性。我的意思是,UB 就是 UB,所以编译器可以自由地解释你的程序,就好像它是使用具有该措辞的语言编写的程序一样......但这就像用核弹打击标准错误一样。

  • @LanguageLawyer,您仍然需要访问 `foo&lt;7,1&gt;::type.__data`:没有用于对齐存储的 typedef,并且在包含数组的类型中构造与在数组中构造不同。 (3认同)
  • @LanguageLawyer 不,为了证明我必须审核整个标准。它们显然不是同一个对象(对象 X 和 X 中包含的数组是不同的对象,即使它们具有相同的大小)。有很多规则说明了它们可以互换的方式。在这种情况下无法证明它们不可互换,因为标准可能包含一个使它们可互换的“任何地方”条款。毕竟我可能是错的。 (3认同)