Edit: Thanks for everyone's answer and replies. Language Lawyer's answer is technically the correct one so that's accepted, but Human-Compiler's answer is the only one that meets the criteria (getting 2+ points) for the bounty, or that is elaborated enough on the question's specific topic.
Full question
Is it defined behavior to have an object b
placed in the coroutine state
(by e.g. having it as a parameter,
or preserving it across a suspension point),
where alignof(b) > __STDCPP_DEFAULT_NEW_ALIGNMENT__
?
Example:
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();
}
Explanation
When a coroutine is called,
heap memory for the coroutine state
is allocated via operator new
.
This call to operator new
may take one of the following forms:
- passing all arguments passed to the coroutine following the size requested, or if no such overloads can be found,
- passing just the size requested.
Whichever form the call takes,
note that it doesn't use the overloads
accepting a std::align_val_t
,
which are necessary to allocate memory
that must be aligned more than __STDCPP_DEFAULT_NEW_ALIGNMENT__
.
Therefore, if an object whose alignment
is larger than __STDCPP_DEFAULT_NEW_ALIGNMENT__
must be saved in the coroutine state,
there should be no way to guarantee
that the object will end up properly aligned
in memory.
Experimentation
async f(): Assertion `reinterpret_cast<uintptr_t>(&b) % 32ull == 0' failed.
so it definitely doesn't work on GCC trunk
(11.0.1 20210307
).
Replacing 32
with 16
(which equals __STDCPP_DEFAULT_NEW_ALIGNMENT__
)
eliminates this assertion failure.
godbolt.org cannot run Windows binaries, but the assertion fires with MSVC on my computer as well.
std::variant/any/optional
? – Parrottoptional
andvariant
don't seem to have this problem. [optional.optional.1]: "The contained value shall be allocated in a region... suitably aligned for the type T." [variant.variant.1]: "The contained value shall be allocated in a region... suitably aligned for all types in Types." It is easy to meet the alignment requirement foroptional
andvariant
by using either a union oraligned_storage_t<sizeof(T), alignof(T)>
as the buffer, and their alignments automatically propagate to the enclosing types. – Nullifidianany
in [any.class]. However, in practice it would be quite difficult to not meet the right alignment anyway, as thenew
expression automatically use thealign_val_t
-accepting overloads for types that have extended alignment. MSVC's, libstdc++'s and libc++'s impls all seem to handle extended alignment correctly (Godbolt). – Nullifidianfor
loop initializer? etc. and neither of these questions should be closed as a dup? – Semitrailer