I switched to fixed-length integer types in my projects mainly because they help me think about integer sizes more clearly when using them. Including them via #include <inttypes.h>
also includes a bunch of other macros like the printing macros PRIu32
, PRIu64
,...
To assign a constant value to a fixed length variable I can use macros like UINT32_C()
and INT32_C()
. I started using them whenever I assigned a constant value.
This leads to code similar to this:
uint64_t i;
for (i = UINT64_C(0); i < UINT64_C(10); i++) { ... }
Now I saw several examples which did not care about that. One is the stdbool.h
include file:
#define bool _Bool
#define false 0
#define true 1
bool
has a size of 1 byte on my machine, so it does not look like an int
. But 0
and 1
should be integers which should be turned automatically into the right type by the compiler. If I would use that in my example the code would be much easier to read:
uint64_t i;
for (i = 0; i < 10; i++) { ... }
So when should I use the fixed length constant macros like UINT32_C()
and when should I leave that work to the compiler(I'm using GCC)? What if I would write code in MISRA C?
As a rule of thumb, you should use them when the type of the literal matters. There are two things to consider: the size and the signedness.
Regarding size:
An int
type is guaranteed by the C standard values up to 32767
. Since you can't get an integer literal with a smaller type than int
, all values smaller than 32767
should not need to use the macros. If you need larger values, then the type of the literal starts to matter and it is a good idea to use those macros.
Regarding signedness:
Integer literals with no suffix are usually of a signed type. This is potentially dangerous, as it can cause all manner of subtle bugs during implicit type promotion. For example (my_uint8_t + 1) << 31
would cause an undefined behavior bug on a 32 bit system, while (my_uint8_t + 1u) << 31
would not.
This is why MISRA has a rule stating that all integer literals should have an u
/U
suffix if the intention is to use unsigned types. So in my example above you could use my_uint8_t + UINT32_C(1)
but you can as well use 1u
, which is perhaps the most readable. Either should be fine for MISRA.
As for why stdbool.h defines true/false to be 1/0, it is because the standard explicitly says so. Boolean conditions in C still use int
type, and not bool
type like in C++, for backwards compatibility reasons.
It is however considered good style to treat boolean conditions as if C had a true boolean type. MISRA-C:2012 has a whole set of rules regarding this concept, called essentially boolean type. This can give better type safety during static analysis and also prevent various bugs.