问题
I read on p0019r8 the following:
atomic_ref(T& obj);
Requires: The referenced object shall be aligned to
required_alignment
.
cppreference interprets this as UB when not aligned:
The behavior is undefined if obj is not aligned to required_alignment.
So how would you expect from an implementation to handle it?
And implementation can check compile-time alignof
, but in reality a type might be aligned more that alignof
. An implementation can interpret pointer bits and check runtime alignment, but it is extra runtime check.
Ultimately I see the following options:
- Do nothing - implement runtime undefined behavior in unpleasant way, favor correct uses only
- Check compile-time alignment (
alignof
) and emit warning if wrong Check compile-time alignment (not correct since actual alignment may be greater than visible by static typealignof
) and fail at compile-time if wrongCheck compile-time alignment (not correct since actual alignment may be greater than visible by static typealignof
) and fail at run-time if wrong- Check compile-time alignment (
alignof
) and fallback to lock-based if wrong - Check run-time alignment (pointer bits) and fail at run-time if wrong
- Check run-time alignment (pointer bits) and fallback to lock-based if wrong
回答1:
TL:DR: never silently fall back to locking, nobody ever wants that because it defeats a major part of the purpose of std::atomic
. Think of non-lock-free as a portability fallback, not a viable mode of operation.
Being UB makes it legal for the compiler to simply assume without any checking that it's aligned. Being able to assume without any runtime checks is one of the major benefits of the concept of UB. This is what most people want / expect at runtime in an optimized build, not bloating the code with conditional branches that might fall back to using a mutex.
The choice of whether (and how) to define any behaviour here is completely up to the implementation, as a matter of Quality-of-Implementation and trading off performance vs. debugging. I think you know that and are literally asking what users want compilers to pick for those QoI choices, which is fine.
As the P0019 proposal you linked says, it all comes down to a QOI issue:
- Reference-ability Constraints
An object referenced by an atomic reference must satisfy possibly architecture-specific constraints. For example, the object might need to be properly aligned in memory or might not be allowed to reside in GPU register memory. We do not enumerate all potential constraints or specify behavior when these constraints are violated. It is a quality-of-implementation issue to generate appropriate information when constraints are violated.
The "generate appropriate information" phrasing implies they expect implementations to warn / error if they detect a violation, not fall back to locking.
Although an implementation that could fall back to locking could foolishly set required_alignment
to the minimum for correctness (1), rather than the minimum for lock-freedom. Of course nobody wants that, but it's a QoI issue not standards-compliance.
I'd expect (or at least hope) for an implementation to work as follows:
Warn at compile time if
atomic_ref
is used on any object whosealignof
is less thanrequired_alignment
. You might know that a certainT *p
happens to be 8 byte aligned even thoughalignof(T)
is only 1 or 4, so this shouldn't be an error.Some local way of silencing the warning would be a good thing. (Alternative: promise alignment to the compiler with something like GNU C
x = __builtin_assume_aligned(x, 16)
)At least warn if an object is definitely known to be under-aligned at compile time, e.g. a sub-member of struct whose alignment is known, or a global var where the declaration is visible but didn't include
alignas
. Warning for access through pointers that might be under-aligned is noisier and should be separately disable-able.Extra-slow Debug mode: runtime check of alignment, warn or abort on a specific object being under-aligned for atomicity. (e.g.
gcc -fsanitize=undefined
, or MSVC's debug mode which already adds stuff likestd::vector::operator[]
bounds checks. I think GCC's UBSan does even more checking than MSVC debug mode, e.g. for signed overflow; I think MSVC debug mode is somewhere in betweengcc -O0
andgcc -O0 -fsanitize=undefined
.)"Release" mode: zero checking, just emit asm whose correctness depends on the object being aligned. (Also gcc
-O0
without UBSan, which allows consistent debugging but doesn't add extra checks.)Nobody ever wants silent fallback to mutexes at compile time or run-time. That mode of operation basically just exists so ISO C++ can require the feature to be supported everywhere without making it impossible to implement on some targets.
The fallback to locking is usually very sub-optimal compared to manual fine-grained locking of a critical section that does a few related atomic ops at once on a data-structure designed for it. People use
atomic<T>
(and the upcomingatomic_ref<T>
) for performance, and much of that performance is destroyed by locking. Especially read-side scalability.
Footnote 1: IIRC, alignof()
is only specified for types, not objects, but in GNU C++ it also works on objects. I'm using this as shorthand for the compiler's internal knowledge that a certain object used alignas()
to over-align it.
来源:https://stackoverflow.com/questions/61996108/atomic-ref-when-external-underlying-type-is-not-aligned-as-requested