I have a variable and the type does not matter; the possible range will easily fit in any of the aforementioned types. For example a loop counter. Etc.
What are the pros and cons of using:
int
unsigned int
int_fastN_t
uint_fastN_t
Note to any potential close-voters: I am not asking which ones to use, because that would be opinion-based, but I am asking, what factors should I consider when choosing one over the other?
int
This type pretty much only has one single benefit and that is backwards-compatibility of non-critical code where integer limits/sizes do not matter. You should almost never use it, unless you are stuck with some backwards compatibility case with old C90.
unsigned int
Same arguments as with int
- this type is mostly to be used in old code where integer sizes don't matter. It is slightly more useful though, because it can be regarded as a safe common type to use in expressions that are subject to implicit integer promotion. If you use unsigned int
or cast an operand to it, then it is no longer subject to integer promotion, default argument promotion and similar. You can also safely use unsigned int
in bit-wise arithmetic, where int
should be avoided.
Furthermore, small integer types like short
, signed char
etc are similarly dangerous because they are subject to implicit promotion, potential signedness change due to integer promotion, potential to hide overflows/wrap-around due to promotion, etc etc.
intn_t
(where n corresponds to the word size of the target)
Compared to int
, the intn_t
has the great advantage of being portable and of fixed width. But only if n is large enough not to make the operand subject of integer promotion, for example int32_t
on a 32/64 bit computer.
intn_t
also has the great advantage of being guaranteed to be in 2's complement form, which is not guaranteed for int
(until the release of C23).
A little detail is that intn_t
isn't guaranteed to be supported unless the target supports it. This doesn't apply to 99% of all real-world systems, only to some oddball DSP CPUs that have 16 bit bytes.
Notably intn_t
should only be used where you actually need signed numbers. If you don't need signed numbers then use the uintn_t
equivalent.
uintn_t
All the same benefits of intn_t
but with yet another benefit, namely that it is also safe to use for bitwise-arithemtic. Making this the most universal integer type of them all.
int_fastn_t
This is mostly used during manual micro-optimization, where size isn't important but you suspect that a larger integer type would result in faster code. It could also have its uses when writing widely portable code.
For example if you develop code with arithmetic that needs to be at least 16 bit and should run on both 16 and 32 bit CPUs. Doing the arithmetic on int16_t
might not be optimal for the 32-bitter because of limited instructions, alignment etc. But if you do the arithmetic on int_fast16_t
then it will not needlessly burden the 16 bitter as 32 bit arithmetic would have, since the type would still be 16 bits on that target. But at the same time it still allows the 32 bitter to pick a larger type if that gives better performance.
Another advantage is that int_fastn_t
is mandatory for all C compilers for 2's complement targets, including freestanding implementations (embedded systems, oddball DSP compilers etc).
The disadvantages vs intn_t
is that int_fastn_t
isn't fixed width, where exact width matters, and that using int_fastn_t
enforces a manual speed-over-size optimization which we might normally let the picked compiler options decide.
uint_fastn_t
Same advantages/disadvantages as int_fastn_t
but is also suitable for bitwise-arithmetic.
int_leastn_t
/uint_leastn_t
These are oddball types with very specialized use. Basically you say that "I need the variable to be at least n bits but other than that you may do as you please". Unlike the fast
types, this gives the compiler more freedom in picking either size or speed optimizations, or not optimization at all.
There's not a lot of use for these types save when you need to optimize for size and at the same time be portable across multiple targets. For example in my 16 vs 32 bit CPU example above, the int_least16_t
would still be 16 bits on the 16 bit CPU, but on the 32 bit CPU the compiler might chose to leave it as 16 bits in case that would give a size benefit.
For example a 32 bit PowerPC with support for VLE encoding (basically: use 16 bit instructions when possible) might benefit from the least
types, since you could leave the C code the same, no matter if the machine code uses VLE or not.
Integer picker program
I hacked together a tl;dr integer picker. Tweak the true/false per #define and let it pick the suitable integer type.
Tested with gcc 13.2 -std=c2x. https://godbolt.org/z/zz9GTf35M
// pick true/false for each of these parameters:
#define CODE_NEEDS_TO_BE_WIDELY_PORTABLE true
#define CODE_MUST_BE_C90 false
#define I_NEED_NEGATIVE_NUMBERS false
#define I_NEED_BITWISE_ARITHMETIC false
#define I_CARE_ABOUT_NUMERICAL_LIMITS true
#define I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS false
_Static_assert(!(CODE_NEEDS_TO_BE_WIDELY_PORTABLE && CODE_MUST_BE_C90),
"Contradicting requirements. C90 isnt portable.");
#include <stdio.h>
int main (void)
{
if(CODE_NEEDS_TO_BE_WIDELY_PORTABLE)
if(!I_NEED_NEGATIVE_NUMBERS || I_NEED_BITWISE_ARITHMETIC)
if(!I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS)
puts("Use uintn_t");
else
puts("Use uint_fastn_t/uint_leastn_t depending on the situation.");
else
if(!I_HAVE_SPECIAL_OPTIMIZATION_REQUIREMENTS)
puts("Use intn_t");
else
puts("Use uint_fastn_t/uint_leastn_t depending on the situation.");
else
if(!I_NEED_NEGATIVE_NUMBERS || I_NEED_BITWISE_ARITHMETIC)
if(!CODE_MUST_BE_C90 || I_CARE_ABOUT_NUMERICAL_LIMITS)
puts("Use uintn_t");
else if(CODE_MUST_BE_C90 && I_CARE_ABOUT_NUMERICAL_LIMITS)
puts("Use home-brewed type system replicating uintn_t from stdint.h");
else
puts("Use unsigned int");
else
if(!CODE_MUST_BE_C90 || I_CARE_ABOUT_NUMERICAL_LIMITS)
puts("Use intn_t");
else if(CODE_MUST_BE_C90 && I_CARE_ABOUT_NUMERICAL_LIMITS)
puts("Use home-brewed type system replicating intn_t from stdint.h");
else
puts("Use int");
}