Why does stdatomic.h contain atomic_uint_least16_t and atomic_uint_fast16_t but not atomic_uint16_t?

689 views Asked by At

stdatomic.h appears to contain atomic_uint_least16_t and atomic_uint_fast16_t, which are _Atomic versions of the stdint.h types uint_least16_t and uint_fast16_t, but it does not contain atomic_uint16_t. Why?


For some background information from the N1548 draft:

7.18.1.1 Exact-width integer types

1 The typedef name intN_t designates a signed integer type with width N, no padding bits, and a two’s complement representation. Thus, int8_t denotes such a signed integer type with a width of exactly 8 bits.

2 The typedef name uintN_t designates an unsigned integer type with width N and no padding bits. Thus, uint24_t denotes such an unsigned integer type with a width of exactly 24 bits.

3 These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.

7.18.1.2 Minimum-width integer types

1 The typedef name int_leastN_t designates a signed integer type with a width of at least N, such that no signed integer type with lesser size has at least the specified width. Thus, int_least32_t denotes a signed integer type with a width of at least 32 bits.

2 The typedef name uint_leastN_t designates an unsigned integer type with a width of at least N, such that no unsigned integer type with lesser size has at least the specified width. Thus, uint_least16_t denotes an unsigned integer type with a width of at least 16 bits.

3 The following types are required:

int_least8_t
int_least16_t
int_least32_t
int_least64_t
uint_least8_t
uint_least16_t
uint_least32_t
uint_least64_t

All other types of this form are optional.

(and so on, to include the int_fastN_t / uint_fastN_t types, etc.)

It is worth highlighting in paragraph 3:

However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.

This means that if, for example, I have a type like int or short which is implemented as a 16-bit integer with two's complement representation, then the implementation shall define int16_t.

The atomic_ types for <stdatomic.h> are also listed in N1548 (reproduced below) but it does not make a corresponding requirement that if the implementation has a int16_t then there is a atomic_int16_t --- that is the nature of my question.

7.17.6 Atomic integer and address types

1 For each line in the following table, the atomic type name is declared as the corresponding direct type.

Atomic type name         Direct type
----------------         -----------
atomic_char              _Atomic char
atomic_schar             _Atomic signed char
atomic_uchar             _Atomic unsigned char
atomic_short             _Atomic short
atomic_ushort            _Atomic unsigned short
atomic_int               _Atomic int
atomic_uint              _Atomic unsigned int
atomic_long              _Atomic long
atomic_ulong             _Atomic unsigned long
atomic_llong             _Atomic long long
atomic_ullong            _Atomic unsigned long long
atomic_char16_t          _Atomic char16_t
atomic_char32_t          _Atomic char32_t
atomic_wchar_t           _Atomic wchar_t
atomic_int_least8_t      _Atomic int_least8_t
atomic_uint_least8_t     _Atomic uint_least8_t
atomic_int_least16_t     _Atomic int_least16_t
atomic_uint_least16_t    _Atomic uint_least16_t
atomic_int_least32_t     _Atomic int_least32_t
atomic_uint_least32_t    _Atomic uint_least32_t
atomic_int_least64_t     _Atomic int_least64_t
atomic_uint_least64_t    _Atomic uint_least64_t
atomic_int_fast8_t       _Atomic int_fast8_t
atomic_uint_fast8_t      _Atomic uint_fast8_t
atomic_int_fast16_t      _Atomic int_fast16_t
atomic_uint_fast16_t     _Atomic uint_fast16_t
atomic_int_fast32_t      _Atomic int_fast32_t
atomic_uint_fast32_t     _Atomic uint_fast32_t
atomic_int_fast64_t      _Atomic int_fast64_t
atomic_uint_fast64_t     _Atomic uint_fast64_t
atomic_intptr_t          _Atomic intptr_t
atomic_uintptr_t         _Atomic uintptr_t
atomic_size_t            _Atomic size_t
atomic_ptrdiff_t         _Atomic ptrdiff_t
atomic_intmax_t          _Atomic intmax_t
atomic_uintmax_t         _Atomic uintmax_t

2 The semantics of the operations on these types are defined in 7.17.7.

3 The atomic_bool type provides an atomic boolean.

4 The atomic_address type provides atomic void * operations. The unit of addition/subtraction shall be one byte.

5 NOTE The representation of atomic integer and address types need not have the same size as their corresponding regular types. They should have the same size whenever possible, as it eases effort required to port existing code.

3

There are 3 answers

13
gnasher729 On

I can only guess, but if you can implement atomic access only to things larger than uint16_t, then implementing atomic access to uint_least16_t and uint_fast16_t can always be done by defining the types accordingly, while atomic access to uint16_t may be just impossible with the available hardware. And you don't want anything in the standard that cannot be implemented.

8
Jens Gustedt On

This list of specialized atomic types are only there because of a historic accident where they were meant to ensure compatibility with C++. And, they were only meant to provide interfaces for integer types that are mandatory. None of the uintXX_t types are mandatory, and therefore they are not included.

(That goal was immediately watered by adding atomic_[u]intprt_t where [u]intptr_t are not mandatory, but that is probably yet another story.)

9
Mecki On

Because a platform may not be able to handle uint16_t in an atomic way. If a platform has no native uint16_t type, a compiler can still emulate that type on top of uint32_t but such an emulated type will never be atomic.

Note that all exact width types are optional to begin with. The C standard only requires uint_least16_t and uint_fast16_t to exist. Both guarantee to have at least 16 bits but they may have more than 16 bits. The difference is that the first one is optimized for space (use as little memory as possible, even if that will be slow) and the second one for performance (use the fastest type available, even if that requires lots of memory).

A compiler may offer uint16_t if such a native type is available on the platform or the compiler wants to emulate it but it is never required to do so. Code that should be compilable with every compiler following the standard, must not rely on uint16_t to exist in the first place.

The POSIX standard requires uint16_t to exist, so for POSIX platforms, the compiler must emulate that type is not natively available, but the POSIX platform does not require any type to be atomic at all.