I suspect sig_atomic_t does work fine when we're talking about POSIX signals, but OP was probably thinking more from an embedded programming and hardware interrupt handlers, which don't conform to POSIX signal semantics.
> I suspect sig_atomic_t does work fine when we're talking about POSIX signals, but OP was probably thinking more from an embedded programming and hardware interrupt handlers, which don't conform to POSIX signal semantics.
It's not the sig_atomic_t that I think is wrong (could be plain int), it's the "Is it safe to have one writer to a zero-initialised volatile value, and many readers checking for non-zero of that value?"
Now I wouldn't use this and expect correctness in the value, but even when the value that is read is corrupted because that single write did not finish (it's zero, one or something else), it will be non-zero eventually, and so the thread will end.
Technically, using volatile between threads is a data race and therefore UB [1]; the guarantees made around sig_atomic_t only apply between a thread and a signal handler on the same thread.
Though, I'd argue that the no-optimization guarantee of volatile actually does justify reasoning of the form "it's not undefined behavior because the hardware guarantees it", which is a mistake anywhere else in C. On essentially all architectures, loads and stores of volatile integers act the same way as loads and stores of atomics using memory_order_relaxed (or stronger, depending on the architecture). So it may be legal to rely on volatile being atomic, as long as you don't expect the code to be compiled on some hypothetical architecture that doesn't have this feature.
At the hardware level, there is no distinction between a regular load, a volatile load, and a relaxed atomic
load (assuming small sizes and optimal alignments). But the compiler can still can do things that break your code or that miss optimizations when given incorrect ordering annotations.
It seems to me that volatile ends up falling almost between acquire and relaxed ordering, in terms of the behavior of most CPUs and compilers, for small aligned values: it doesn’t synchronize any other operation but does prevent folding of consecutive operations.