It seems that uint32_t
is much more prevalent than uint_fast32_t
(I realise this is anecdotal evidence). That seems counter-intuitive to me, though.
One reason is that unsigned int
is already "fastest" without the need for any special typedefs or the need to include something. So, if you need it fast, just use the fundamental int
or unsigned int
type.
While the standard does not explicitly guarantee that it is fastest, it indirectly does so by stating "Plain ints have the natural size suggested by the architecture of the execution environment" in 3.9.1. In other words, int
(or its unsigned counterpart) is what the processor is most comfortable with.
Now of course, you don't know what size unsigned int
might be. You only know it is at least as large as short
(and I seem to remember that short
must be at least 16 bits, although I can't find that in the standard now!). Usually it's just plain simply 4 bytes, but it could in theory be larger, or in extreme cases, even smaller (although I've personally never encountered an architecture where this was the case, not even on 8-bit computers in the 1980s... maybe some microcontrollers, who knows turns out I suffer from dementia, int
was very clearly 16 bits back then).
The C++ standard doesn't bother to specify what the <cstdint>
types are or what they guarantee, it merely mentions "same as in C".
uint32_t
, per the C standard, guarantees that you get exactly 32 bits. Not anything different, none less and no padding bits. Sometimes this is exactly what you need, and thus it is very valuable.
uint_least32_t
guarantees that whatever the size is, it cannot be smaller than 32 bits (but it could very well be larger). Sometimes, but much more rarely than an exact witdh or "don't care", this is what you want.
Lastly, uint_fast32_t
is somewhat superfluous in my opinion, except for documentation-of-intent purposes. The C standard states "designates an integer type that is usually fastest" (note the word "usually") and explicitly mentions that it needs not be fastest for all purposes. In other words, uint_fast32_t
is just about the same as uint_least32_t
, which is usually fastest too, only no guarantee given (but no guarantee either way).
Since most of the time you either don't care about the exact size or you want exactly 32 (or 64, sometimes 16) bits, and since the "don't care" unsigned int
type is fastest anyway, this explains why uint_fast32_t
isn't so frequently used.
Why do many people use
uint32_t
rather thanuint32_fast_t
?
Silly answer:
uint32_fast_t
, the correct spelling is uint_fast32_t
.Practical answer:
uint32_t
or int32_t
for their precise semantics, exactly 32 bits with unsigned wrap around arithmetic (uint32_t
) or 2's complement representation (int32_t
). The xxx_fast32_t
types may be larger and thus inappropriate to store to binary files, use in packed arrays and structures, or send over a network. Furthermore, they may not even be faster.Pragmatic answer:
uint_fast32_t
, as demonstrated in comments and answers, and probably assume plain unsigned int
to have the same semantics, although many current architectures still have 16-bit int
s and some rare Museum samples have other strange int sizes less than 32.UX answer:
uint32_t
, uint_fast32_t
is slower to use: it takes longer to type, especially accounting for looking up spelling and semantics in the C documentation ;-)Elegance matters, (obviously opinion based):
uint32_t
looks bad enough that many programmers prefer to define their own u32
or uint32
type... From this perspective, uint_fast32_t
looks clumsy beyond repair. No surprise it sits on the bench with its friends uint_least32_t
and such.To my understanding, int
was initially supposed to be a "native" integer type with additional guarantee that it should be at least 16 bits in size - something that was considered "reasonable" size back then.
When 32-bit platforms became more common, we can say that "reasonable" size has changed to 32 bits:
int
on all platforms.int
is at least 32 bits.int
which is guaranteed to be exactly 32 bits.But when 64-bit platform became the norm, no one expanded int
to be a 64-bit integer because of:
int
being 32 bit in size.int
might be unreasonable for most cases, as in most cases numbers in use are much smaller than 2 billion.Now, why would you prefer uint32_t
to uint_fast32_t
? For the same reason languages, C# and Java always use fixed size integers: programmer does not write code thinking about possible sizes of different types, they write for one platform and test code on that platform. Most of the code implicitly depends on specific sizes of data types. And this is why uint32_t
is a better choice for most cases - it does not allow any ambiguity regarding its behavior.
Moreover, is uint_fast32_t
really the fastest type on a platform with a size equal or greater to 32 bits? Not really. Consider this code compiler by GCC for x86_64 on Windows:
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
Generated assembly looks like this:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
Now if you change get()
's return value to uint_fast32_t
(which is 4 bytes on Windows x86_64) you get this:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
mov %eax,%eax ; <-- additional instruction
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
Notice how generated code is almost the same except for additional mov %eax,%eax
instruction after function call which is meant to expand 32-bit value into a 64-bit value.
There is no such issue if you only use 32-bit values, but you will probably be using those with size_t
variables (array sizes probably?) and those are 64 bits on x86_64. On Linux uint_fast32_t
is 8 bytes, so the situation is different.
Many programmers use int
when they need to return small value (let's say in the range [-32,32]). This would work perfectly if int
would be platforms native integer size, but since it is not on 64-bit platforms, another type which matches platform native type is a better choice (unless it is frequently used with other integers of smaller size).
Basically, regardless of what standard says, uint_fast32_t
is broken on some implementations anyway. If you care about additional instruction generated in some places, you should define your own "native" integer type. Or you can use size_t
for this purpose, as it will usually match native
size (I am not including old and obscure platforms like 8086, only platforms that can run Windows, Linux etc).
Another sign that shows int
was supposed to be a native integer type is "integer promotion rule". Most CPUs can only perform operations on native, so 32 bit CPU usually can only do 32-bit additions, subtractions etc (Intel CPUs are an exception here). Integer types of other sizes are supported only through load and store instructions. For example, the 8-bit value should be loaded with appropriate "load 8-bit signed" or "load 8-bit unsigned" instruction and will expand value to 32 bits after load. Without integer promotion rule C compilers would have to add a little bit more code for expressions that use types smaller than native type. Unfortunately, this does not hold anymore with 64-bit architectures as compilers now have to emit additional instructions in some cases (as was shown above).
From the viewpoint of correctness and ease of coding, uint32_t
has many advantages over uint_fast32_t
in particular because of the more precisely defined size and arithmetic semantics, as many users above have pointed out.
What has perhaps been missed is that the one supposed advantage of uint_fast32_t
- that it can be faster, just never materialized in any meaningful way. Most of the 64-bit processors that have dominated the 64-bit era (x86-64 and Aarch64 mostly) evolved from 32-bit architectures and have fast 32-bit native operations even in 64-bit mode. So uint_fast32_t
is just the same as uint32_t
on those platforms.
Even if some of the "also ran" platforms like POWER, MIPS64, SPARC only offer 64-bit ALU operations, the vast majority of interesting 32-bit operations can be done just fine on 64-bit registers: the bottom 32-bit will have the desired results (and all mainstream platforms at least allow you to load/store 32-bits). Left shift is the main problematic one, but even that can be optimized in many cases by value/range tracking optimizations in the compiler.
I doubt the occasional slightly slower left shift or 32x32 -> 64 multiplication is going to outweigh double the memory use for such values, in all but the most obscure applications.
Finally, I'll note that while the tradeoff has largely been characterized as "memory use and vectorization potential" (in favor of uint32_t
) versus instruction count/speed (in favor of uint_fast32_t
) - even that isn't clear to me. Yes, on some platforms you'll need additional instructions for some 32-bit operations, but you'll also save some instructions because:
struct two32{ uint32_t a, b; }
into rax
like two32{1, 2}
can be optimized into a single mov rax, 0x20001
while the 64-bit version needs two instructions. In principle this should also be possible for adjacent arithmetic operations (same operation, different operand), but I haven't seen it in practice.Smaller data types often exploit better modern calling conventions like the SysV ABI which pack data structure data efficiently into registers. For example, you can return up to a 16-byte structure in registers rdx:rax
. For a function returning structure with 4 uint32_t
values (initialized from a constant), that translates into
ret_constant32():
movabs rax, 8589934593
movabs rdx, 17179869187
ret
The same structure with 4 64-bit uint_fast32_t
needs a register move and four stores to memory to do the same thing (and the caller will probablyhave to read the values back from memory after the return):
ret_constant64():
mov rax, rdi
mov QWORD PTR [rdi], 1
mov QWORD PTR [rdi+8], 2
mov QWORD PTR [rdi+16], 3
mov QWORD PTR [rdi+24], 4
ret
Similarly, when passing structure arguments, 32-bit values are packed about twice as densely into the registers available for parameters, so it makes it less likely that you'll run out of register arguments and have to spill to the stack1.
Even if you choose to use uint_fast32_t
for places where "speed matters" you'll often also have places where you need a fixed size type. For example, when passing values for external output, from external input, as part of your ABI, as part of a structure that needs a specific layout, or because you smartly use uint32_t
for large aggregations of values to save on memory footprint. In the places where your uint_fast32_t
and ``uint32_t` types need to interface, you might find (in addition to the development complexity), unnecessary sign extensions or other size-mismatch related code. Compilers do an OK job at optimizing this away in many cases, but it still not unusual to see this in optimized output when mixing types of different sizes.
You can play with some of the examples above and more on godbolt.
1 To be clear, the convention of packing structures tightly into registers isn't always a clear win for smaller values. It does mean that the smaller values may have to be "extracted" before they can be used. For example a simple function that returns the sum of the two structure members together needs a mov rax, rdi; shr rax, 32; add edi, eax
while for the 64-bit version each argument gets its own register and just needs a single add
or lea
. Still if you accept that the "tightly pack structures while passing" design makes sense overall, then smaller values will take more advantage of this feature.
To give a direct answer: I think the real reason why uint32_t
is used over uint_fast32_t
or uint_least32_t
is simply that it is easier to type, and, due to being shorter, much nicer to read: If you make structs with some types, and some of them are uint_fast32_t
or similar, then it's often hard to align them nicely with int
or bool
or other types in C, which are quite short (case in point: char
vs. character
). I of course cannot back this up with hard data, but the other answers can only guess at the reason as well.
As for technical reasons to prefer uint32_t
, I don't think there are any - when you absolutely need an exact 32 bit unsigned int, then this type is your only standardised choice. In almost all other cases, the other variants are technically preferable - specifically, uint_fast32_t
if you are concerned about speed, and uint_least32_t
if you are concerned about storage space. Using uint32_t
in either of these cases risks not being able to compile as the type is not required to exist.
In practise, the uint32_t
and related types exist on all current platforms, except some very rare (nowadays) DSPs or joke implementations, so there is little actual risk in using the exact type. Similarly, while you can run into speed penalties with the fixed-width types, they are (on modern cpus) not crippling anymore.
Which is why, I think, the shorter type simply wins out in most cases, due to programmer lazyness.
I have not seen evidence that uint32_t
be used for its range. Instead, most of the time that I've seen uint32_t
is used, it is to hold exactly 4 octets of data in various algorithms, with guaranteed wraparound and shift semantics!
There are also other reasons to use uint32_t
instead of uint_fast32_t
: Often it is that it will provide stable ABI. Additionally the memory usage can be known accurately. This very much offsets whatever the speed gain would be from uint_fast32_t
, whenever that type would be distinct from that of uint32_t
.
For values < 65536, there is already a handy type, it is called unsigned int
(unsigned short
is required to have at least that range as well, but unsigned int
is of the native word size) For values < 4294967296, there is another called unsigned long
.
And lastly, people do not use uint_fast32_t
because it is annoyingly long to type and easy to mistype :D