C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Draft proposal for fixing undefined behavior in `atomic_notify_*` operations

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Thu, 15 Jan 2026 21:36:55 +0000
On Thu, 15 Jan 2026 at 19:50, Ryan P. Nicholl <rnicholl_at_[hidden]>
wrote:

> The implementation of std::atomic_notify_* on libc++, gcc, musl and
> microsoft STL is already in compliance with the proposed wording change.
>

Your wording talks about a "pointer to M" but if M is outside its lifetime,
then it's an invalid pointer value. You would need to talk about a pointer
to the storage of M, otherwise your wording doesn't achieve what you want.

You never replied to my question about what it means if the memory has been
deallocated and is not even part of the address space. Linux's futex(2) and
FreeBSD's _umtx_op should return EFAULT for that, but your proposal says
waiters might be spuriously unblocked, it doesn't allow a waiting operation
to fail (potentially terminating, depending how the implementation handles
errors from the system call). If the storage has already been reused for
another atomic of a different type, worse things can happen than spuriously
unblocking waits on the new object: macOS will return EINVAL if the wake
and wait calls don't agree on the size of the value at that address.


>
> They are *already* fully implemented, this proposal would only be a
> standardese fix. You cannot tell me this is not possible to do, it is
> already done. I can write code that will work fine on all platforms, but
> according to the standard C++ language it's not allowed. *AFAIK, all
> platforms already do this.* If you know an implementation that doesn't do
> this with std::atomic_notify_*, I would like to know what that is.
>

GCC's implementation immediately dereferences the pointer parameter of
atomic_notify_one, which is undefined if the std::atomic object is outside
its lifetime. It would also not be valid in a constant expression, but that
function is constexpr now, so you need to consider what should happen
there. Changing those properties of the implementation wouldn't be
difficult if needed, but it's not true that they already implement what you
want. It's only true if you say that what is undefined in the abstract
machine is fine, because it happens to work.

Within the implementation of __atomic_wait_address there are operations
such as casts and lvalue-to-rvalue conversions which are
implementation-defined if the pointer is an invalid pointer value, such as
a pointer to a region of storage that no longer contains an object.




> Saying "this can't be done" without doing any investigation on your own is
> not that productive.
>

Saying "this can be done" when you're talking about requiring operations on
invalid pointer values outside of an object's lifetime is just brushing
aside a lot of rules of the abstract machine. Again, you opened the entire
discussion talking about the abstract machine, but the proposal is
basically requiring atomic_notify_one to operate entirely outside the
abstract machine, treating its parameter as an opaque pattern of bits with
no relation to an object (it's hard to interpret the wording any other way
if the calls are allowed invalid pointer values as arguments, or can race
with destructors of the object).



> There are certainly some OS where you can't directly wait on the atomic
> address itself if the wait area is larger than the atomic region, using a
> *single* system call, that doesn't mean it isn't possible to do using
> another algorithm, though. As it turns out, address monitor queues work for
> an arbitrarily sized type. Is it a bit slower? Yes. Is it unimplementable?
> No. Do all atomic implementations use monitor queues for types that don't
> fit in the os sync primitives? As far as I can tell, yes. And of course,
> even lock based atomics work fine with monitor queues too, so I see no
> reason not to allow it. If you can wait on any value of any size, you can
> create a monitor queue for values of arbitrary sizes.
>
>

Received on 2026-01-15 21:37:12