如何解决将奇特对齐的对象置于协程状态是否是定义的行为? 完整问题说明实验
编辑:感谢大家的回答和回复。 Language Lawyer 的答案在技术上是正确的,因此可以接受,但 Human-Compiler 的答案是唯一符合赏金标准(获得 2+ 分),或者对问题的特定主题进行了足够详细阐述的答案。
完整问题
拥有对象 b
是否定义行为
置于协程状态
(例如将其作为参数,
或将其保存在悬挂点上),
哪里alignof(b) > __STDCPP_DEFAULT_NEW_ALIGNMENT__
?
示例:
inline constexpr size_t large_alignment =
__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2;
struct alignas(large_alignment) behemoth {
void attack();
unsigned char data[large_alignment];
};
task<void> invade(task_queue &q) {
behemoth b{};
co_await submit_to(q);
b.attack();
}
说明
当一个协程被调用时,
协程状态的堆内存
通过 operator new
分配。
这个对operator new
的调用
可以采用以下形式之一:
- 传递所有传递给协程的参数 按照要求的尺寸, 或者如果找不到此类重载,
- 仅传递请求的大小。
无论呼叫采用哪种形式,
请注意,它不使用重载
接受 std::align_val_t
,
哪些是分配内存所必需的
必须比 __STDCPP_DEFAULT_NEW_ALIGNMENT__
对齐。
因此,如果一个对象的对齐方式
大于 __STDCPP_DEFAULT_NEW_ALIGNMENT__
必须保存在协程状态,
应该没有办法保证
对象最终会正确对齐
记忆中。
实验
async f(): Assertion `reinterpret_cast<uintptr_t>(&b) % 32ull == 0' Failed.
所以它绝对不能在 GCC 主干上工作
(11.0.1 20210307
)。
将 32
替换为 16
(等于 __STDCPP_DEFAULT_NEW_ALIGNMENT__
)
消除了这个断言失败。
godbolt.org 无法运行 Windows 二进制文件, 但断言也在我的电脑上用 MSVC 触发。
解决方法
根据我的阅读,这将是未定义的行为。
dcl.fct.def.coroutine/9 涵盖了用于确定协程需要额外存储时将使用的分配函数的查找顺序。查找顺序很清楚:
一个实现可能需要为协程分配额外的存储空间。 这种存储称为协程状态,通过调用非数组分配函数([basic.stc.dynamic.allocation])获得。
在promise 类型的范围内查找分配函数的名称。
如果此查找失败,则会在全局范围内查找分配函数的名称。
如果查找在 promise 类型的范围内找到分配函数,则对通过组装参数列表创建的函数调用执行重载解析。
第一个参数是请求的空间量,类型为 std :: size_t。
左值 p1
…pn
是后续参数。
如果没有找到可行的函数([over.match.viable]),在通过传递所需的空间量作为 std:: 类型的参数创建的函数调用上再次执行重载解析size_t。
(强调我的)
这明确提到它将调用的 new
重载必须以 std::size_t
参数开头,并且可以选择对左值引用列表 p1
、p2
进行操作, ...,pn
(如果它在 promise 的范围内)。
由于在上面的示例中没有为承诺类型定义自定义 operator new
,这意味着它必须选择 ::operator new(std::size_t)
作为重载。
如您所知,::operator new
只能保证与 __STDCPP_DEFAULT_NEW_ALIGNMENT__
对齐——这低于协程存储所需的扩展对齐。这有效地使协程中的任何扩展对齐类型由于未对齐而成为未定义行为。
由于必须调用 ::operator new(std::size_t)
的措辞非常严格,因此这在任何正确实现 c++20
的系统上都应该是一致的。如果一个实现选择支持扩展对齐类型,那么它在技术上会通过调用错误的 new
重载(这将是一个可观察到的偏差)而违反标准。
从分配函数的重载决议的措辞来看,我认为在您需要扩展对齐的情况下,您应该为您的承诺定义一个基于成员的operator new
,它知道可能对齐要求。
简单回顾一下这个问题迄今为止收到的回复:
-
正如@LanguageLawyer 指出的那样,编译器没有义务在任何上下文中支持扩展对齐类型,并且允许这些类型的支持程度在不同的上下文中有所不同。因此,编译器不保证
b
正确对齐是一致的。 -
即使标准要求始终使用
operator new
的非扩展对齐版本,编译器仍然可以通过过度分配和选择在运行时充分对齐的地址。但同样,从技术上讲,它不必支持任何东西。 -
在今天的现状下,OP 的代码示例肯定是不正确的,尽管在更严格的意义上它只是未定义的行为,当在运行时
b
实际上被放错位置并导致某种形式的未对齐的访问(UB 是运行时属性)。
是否支持任何扩展对齐以及支持它们的上下文由实现定义。
所以答案是:实现定义。这句话的第二部分听起来有可能一个实现可能支持在普通函数中创建过度对齐类型的对象,而在协程中不支持。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。