The UINT8_C
macro is defined in "stdint.h", with the following specification: The macro UINTN_C(value)
shall expand to an integer constant expression corresponding to the type uint_leastN_t
.
In the wild, however, implementations differ:
#define UINT8_C(value) ((uint8_t) __CONCAT(value, U)) // AVR-libc
#define UINT8_C(x_) (static_cast<std::uint8_t>(x_)) // QP/C++
#define UINT8_C(c) c // GNU C Library
The first two implementations seem roughly equivalent, but the third one behaves differently: for example, the following program prints 1
with AVR-libc and QP/C++, but -1
with glibc (because right shifts on signed values propagate the sign bit).
std::cout << (UINT8_C(-1) >> 7) << std::endl; // prints -1 in glibc
The implementation of UINT16_C
displays the same behavior, but not UINT32_C
, because its definition includes the U
suffix:
#define UINT32_C(c) c ## U
Interestingly, glibc's definition of UINT8_C
changed in 2006, due to a bug report. The previous definition was #define UINT8_C(c) c ## U
, but that produced incorrect output (false
) on -1 < UINT8_C(0)
due to integer promotion rules.
Are all three definitions correct according to the standard? Are there other differences (besides the handling of negative constants) between these three implementations?
h is a header file in the C standard library introduced in the C99 standard library section 7.18 to allow programmers to write more portable code by providing a set of typedefs that specify exact-width integer types, together with the defined minimum and maximum allowable values for each type, using macros .
The UINT8_C macro is defined in "stdint. h", with the following specification: The macro UINTN_C(value) shall expand to an integer constant expression corresponding to the type uint_leastN_t .
If an int
can represent all the values of a uint_least8_t
then the GNU implementation of the UINT8_C(value)
macro as #define UINT8_C(c) c
conforms to the C standard.
As per C11 7.20.4 Macros for integer constants paragraph 2:
The argument in any instance of these macros shall be an unsuffixed integer constant (as defined in 6.4.4.1) with a value that does not exceed the limits for the corresponding type.
For example, if UINT_LEAST8_MAX
is 255, the following usage examples are legal:
UINT8_C(0)
UINT8_C(255)
UINT8_C(0377)
UINT8_C(0xff)
But the following usage examples result in undefined behavior:
UINT8_C(-1)
— not an integer constant as defined in 6.4.4.1
UINT8_C(1u)
— not an unsuffixed integer constantUINT8_C(256)
— exceeds the limits of uint_least8_t
for this implementationThe signed equivalent INT8_C(-1)
is also undefined behavior for the same reasons.
If UINT_LEAST8_MAX
is 255, a legal instance of UINT8_C(value)
will expand to an integer constant expression and its type will be int
due to integer promotions, as per paragraph 3:
Each invocation of one of these macros shall expand to an integer constant expression suitable for use in
#if
preprocessing directives. The type of the expression shall have the same type as would an expression of the corresponding type converted according to the integer promotions. The value of the expression shall be that of the argument.
Thus for any legal invocation of UINT8_C(value)
, the expansion of this to value
by any implementation where an int
can represent all the values of uint_least8_t
is perfectly standard conforming. For any illegal invocation of UINT8_C(value)
you may not get the result you were expecting due to undefined behavior.
[EDIT added for completeness] As pointed out in cpplearner's answer, the other implementations of UINT8_C(value)
shown in OP's question are invalid because they expand to expressions that are not suitable for use in #if
processing directives.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With