80

The C99 standard introduces the following datatypes. The documentation can be found here for the AVR stdint library.

  • uint8_t means it's an 8-bit unsigned type.
  • uint_fast8_t means it's the fastest unsigned int with at least 8 bits.
  • uint_least8_t means it's an unsigned int with at least 8 bits.

I understand uint8_t and what is uint_fast8_t( I don't know how it's implemented in register level).

1.Can you explain what is the meaning of "it's an unsigned int with at least 8 bits"?

2.How uint_fast8_t and uint_least8_t help increase efficiency/code space compared to the uint8_t?

Mohan
  • 1,781
  • 17
  • 28
mic
  • 985
  • 1
  • 8
  • 8
  • For your 1st question I can imagine that whereas `uint8_t` is guaranteed to be 8 bits, `uint_fast8_t` is guaranteed to be >= 8 bits, much like an `unsigned char`. – jacob Jan 28 '16 at 07:24
  • 1
    One consideration is that `uint8_t` doesn't exist on systems that don't have a native 8-bit type. The other two will be there. – Pete Becker Jan 28 '16 at 14:04
  • 9
    You've gotten answers that refer to "obscure" and "exotic" architectures. Those terms are a bit biased. Sure, if your only experience is with desktop systems, these architectures are outside your range of experience. But "I haven't seen this before" is not the same as "this is obscure or exotic". For people who work with embedded systems or DSPs these things are quite common. – Pete Becker Jan 28 '16 at 14:08
  • 4
    Possible duplicate of [The difference of int8\_t, int\_least8\_t and int\_fast8\_t?](http://stackoverflow.com/questions/5254051/the-difference-of-int8-t-int-least8-t-and-int-fast8-t) – dan04 Jan 28 '16 at 23:24

7 Answers7

104

uint_least8_t is the smallest type that has at least 8 bits. uint_fast8_t is the fastest type that has at least 8 bits.

You can see the differences by imagining exotic architectures. Imagine a 20-bit architecture. Its unsigned int has 20 bits (one register), and its unsigned char has 10 bits. So sizeof(int) == 2, but using char types requires extra instructions to cut the registers in half. Then:

  • uint8_t: is undefined (no 8 bit type).
  • uint_least8_t: is unsigned char, the smallest type that is at least 8 bits.
  • uint_fast8_t: is unsigned int, because in my imaginary architecture, a half-register variable is slower than a full-register one.
rodrigo
  • 79,651
  • 7
  • 121
  • 162
  • 12
    I love how you have to imagine exotic architectures to find a use case for this. Have they found any usefulness in practice? – user541686 Jan 28 '16 at 07:28
  • 19
    @Mehrdad In ARM for example, if your `int_fast8_t` is 32-bit variable, you don't need to do sign extension before arithmetric operations. – user694733 Jan 28 '16 at 07:30
  • 1
    @Mehrdad MIPS for example it would be very wrong to make any `uintX_fast_t` less than 32 bits. You don't even have to imagine architectures to get `uint8_t` to be undefined, take for example UNIVAC which is 36-bit, I'd assume that there `char` is 9-bit. – skyking Jan 28 '16 at 08:01
  • @skyking: I was asking whether the types have been **useful** on any architectures. In other words, do people actually use them and benefit from their semantics across architectures? Or are they just gathering dust or being used when they are not actually needed or useful? – user541686 Jan 28 '16 at 08:14
  • 7
    @Mehrdad: I admit that I've never seen the `uint_leastX_t` or `uint_fastX_t` used in real world applications. `uintX_t` yes, they are heavily used. It looks like people are not very interesting in portability to exotic architectures. Which is expected, even if you get your unsigneds right, your program will fail at a thousand different things. – rodrigo Jan 28 '16 at 08:36
  • `int_fast32_t` is plausible: for situations where you'd normally use `int` but don't want it to break on 16-bit platform – M.M Jan 28 '16 at 08:39
  • @Mehrdad I'd say it's quite useful, at least the `*int_least*_t` and `uint_fast*_t`. They are guaranteed to exist and guaranteed to have the stated range - this means that the code actually becomes portable. Of course one could argue that one could use `int` instead of `int_least16_t` and `long` instead of `int_least32_t` (but note how this makes you use 32-bit numbers instead of 16-bit where 16-bit is available, or 64-bit numbers instead of 32-bit where 32-bit is available). – skyking Jan 28 '16 at 08:40
  • 1
    @Mehrdad: ...and if you are programming for an exotic architecture, then you know your types, you are unlikely to write code useful for another computer, so you don't care about portability. – rodrigo Jan 28 '16 at 08:41
  • 1
    @rodrigo Why would one not care about portability? My experience is that people on mainstream architecture are more inclined to assume that every platform that behaves slightly different is "broken" and that one would not have to care about `int` not being suitable to hold a pointer. – skyking Jan 28 '16 at 08:44
  • 1
    @skyking: Sure they are useful, in theory. But I did a quick grep through the source code of a few OS popular programs and libraries I have around, and the only uses of these types are in embedded copies of `stdint.h`. And this header is only required for the `uintX_t` ones. – rodrigo Jan 28 '16 at 08:44
  • [Example of usage in non-exotic systems.](http://embeddedgurus.com/stack-overflow/2011/02/efficient-c-tip-13-use-the-modulus-operator-with-caution/) – user694733 Jan 28 '16 at 08:45
  • 3
    @skyking: I'm not saying they should not be used, just that they are not used very much in practice. If you can find a real-world application or library that uses them sensibly, then post a link, because I couldn't find any. – rodrigo Jan 28 '16 at 08:46
  • @rodrigo Yes, in theory one should be concerned with integer overflow, but in practice it seems that programmers just assume that those does not happen (then comes Y2K that shouldn't happen). – skyking Jan 28 '16 at 08:48
  • For anyone for whom it might have been unclear -- what I've been trying to say earlier are the following two things: (1) Yes, I have seen some ultra-correct people use `[u]int_{least,fast}*_t` in their code, but (2) I have yet to see anyone do this because it has provided them with an *actual* benefit, rather than a hypothetical one. In other words, in the use cases I've seen, the people would have (in practice) been just as well off using `unsigned int` or `unsigned char` or `size_t` or whatever. – user541686 Jan 28 '16 at 09:00
  • 1
    @Mehrdad: The advantage is in context. If you write `unsigned int`, it is not clear *why* you used *that* type (and not some other). Is it because it's *exactly* 32 bit, or because it's *at least* 32 bit? Or is it because that's the "fastest" type? (On *your* platform...) I -- as a maintenance coder -- don't know your intentions in using `unsigned int`, and I will have doubts that it might be the wrong type in the context (and thus responsible for the bug I am hunting). If you wrote `uint_least32_t`, your *intention* is clear, and I can double-check if your assumptions were / are correct. – DevSolar Jan 28 '16 at 12:07
  • @merhad: I've worked with a CPU that only had 32 bit values for everything. (Actually 40 bits, but the compiler made everything into 32 bit values), so it had no uint8_t or uint16_t, but uint32_t and all the uint_least* and uint_fast* types were the same as uint32_t. This was an ADSP SHARC. In my current project we are using the fast and least types a lot. – Florian Keßeler Jan 28 '16 at 12:56
  • @DevSolar: Unfortunately, the semantics of the `least` and `fast` types in mixed-type expressions are a total mess. For example, given `uint_least8_t x=1;` what should be the value of `x-2 > 5`? – supercat Jan 29 '16 at 17:18
  • @supercat: "Don't rely on integer overflow / underflow semantics, unsigned or not"? – DevSolar Jan 30 '16 at 07:00
  • @DevSolar: With `uint8_t` there would be no integer overflow, since 2 is signed. Perhaps it would be more helpful to have separate least "number" and "wrapping algebraic ring" types, so a `unum_least8_t` could be any type--signed or unsigned--that can hold 0-255 *and promotes to a signed type* while `uwrap_least8_t` would be a type which promotes to something which when added, subtrated, multiplied, etc. will yield a result whose bottom 8 bits will be as though the operation were performed on an 8-bit type. – supercat Jan 30 '16 at 16:56
  • 1
    Exotic architectures, like machines with 9-bit bytes, make this easy to understand. (By the way, at least one widespread ESP has 9 bit bytes, and 36-bit "words" in registers. The extra bits are used as guard bits to prevent overflow/saturation. They are thrown away when stored to memory.) // But, this applies even to common RISCs with 32-bit reg-reg instructions. uint8_t would require truncation - eg by AND or by store -byte/load-byte - after every uint8_t operation. I think that uint_fast8_t is allowed to sometimes truncate, and sometimes not, even of sizeof(uint8_t) == sizeof(uint_fast8_t). – Krazy Glew Feb 13 '16 at 23:16
  • "For example, given uint_least8_t x=1; what should be the value of x-2 > 5?" 1 if char is the same size as int 0 if char is smaller than int. – plugwash Apr 18 '17 at 17:01
  • @plugwash: `x` is integral promoted before the substraction, so if its type is the same size as `int` it will be promoted to `unsigned int` and the result will be true. If it is smaller it will be promoted to `int` and the result will be false. – rodrigo Apr 18 '17 at 18:29
  • @supercat if you write `uint[ANY]_t a=1; a-2;` you are using implementation-defined behaviour and it is clearly stated in standard. – Euri Pinhollow Jul 28 '17 at 18:53
  • @EuriPinhollow: Implementations are free to support `uint16_t` or not, making the *existence* of `uint16_t` is Implementation Defined, but `uint16_t a=1; a-=2;` will set `a` to 65535 on any conforming implementation that supports `uint16_t` doesn't invoke the "One Program Rule" to justify doing something arbitrarily different. – supercat Jul 28 '17 at 19:59
  • @supercat you are right, standard actually says that: http://eel.is/c++draft/conv.integral#2 even though not only two's complement is allowed. – Euri Pinhollow Jul 30 '17 at 09:10
  • @EuriPinhollow: The conversion from signed types to unsigned is defined such that subtracting any positive `N` from any unsigned type and then adding `N` will yield the original value when the result is coerced to the original unsigned type [I say a positive value, because one could contrive a weird system where `int` had 16 value bits, a sign bit, and 15 bits of padding, and `unsigned` had 16 value bits and 16 bits of padding, and `uint16_t` ranked below `int`, where subtracting -1 from a `uint16_t` would yield Undefined Behavior]. On the other hand, ... – supercat Jul 30 '17 at 16:22
  • ...so far as I'm aware no C implementations that don't use two's-complement have ever supported 64-bit or larger unsigned type, which would make it unlikely that any conforming non-two's-complement C99 or C11 implementations will ever exist. – supercat Jul 30 '17 at 16:24
  • If you are working for `POSIX` systems your don't need `least` types, you can use `uintn_t` directly. `POSIX` systems has a requirement `CHAR_BIT == 8`. I am just using fast and precise types. – puchu Dec 08 '18 at 17:11
30

uint8_t means: give me an unsigned int of exactly 8 bits.

uint_least8_t means: give me the smallest type of unsigned int which has at least 8 bits. Optimize for memory consumption.

uint_fast8_t means: give me an unsigned int of at least 8 bits. Pick a larger type if it will make my program faster, because of alignment considerations. Optimize for speed.

Also, unlike the plain int types, the signed version of the above stdint.h types are guaranteed to be 2's complement format.

Lundin
  • 155,020
  • 33
  • 213
  • 341
  • 1
    Thanks. Good to know that the signed types in `stdint.h` are guaranteed to be two's complement. Wonder where it will help when writing portable code. – legends2k Jan 28 '16 at 07:46
  • 6
    Note that only the exact width variants are required to use 2's complement format. Also note that these are not required to exist. Consequently a platform is not required to support 2's complement format. – skyking Jan 28 '16 at 07:52
  • @legends2k: The types in `stdint.h` are rather less helpful than one might like if one is trying to write portable code, since while they are required to use two's-complement storage format, that does not imply that they will exhibit two's-complement wrapping behavior. Note also that *even on platforms where `int` is 32 bits*, writing a value using an `int32_t*` and reading using an `int*`, or vice versa, is not guaranteed to work. – supercat Jan 29 '16 at 17:29
  • @supercat Every compiler I have seen use an internal typedef for the stdint.h types, to make them synonymous with one of the basic "keyword" integer types. So if you worry about pointer aliasing I don't think that's going to be an issue in practice, only in theory. – Lundin Feb 01 '16 at 07:19
  • @Lundin: Some compilers use "long" as the typedef for int32_t, and some use "int". Even when "int" and "long" have the same representation they may be (and sometimes are) considered distinct for purposes of C's aliasing rules. – supercat Feb 01 '16 at 14:43
  • @supercat Wouldn't it be quite stupid of the compiler to typedef it as a type that causes conflict with each own aliasing handling? Seems like a problem for platform-independent compilers only (GCC), where the compiler port for a given hardware has no control over how pointer aliasing is done? – Lundin Feb 01 '16 at 15:10
  • @Lundin: Any particular compiler will likely define "int32_t" and "uint32_t" to "int" and "unsigned int", or to "long" and "unsigned long". The problem is that on platforms where "int" and "long" are both 32 bits, there's no particular reason to expect "int32_t" to be one size or the other. If one is passing arrays to libraries which use `*int`, `*long`, and `*int32_t`, there's no way to make one array be compatible with all three *even on platforms where all three types have the same size and representation*. – supercat Feb 01 '16 at 17:39
27

The theory goes something like:

uint8_t is required to be exactly 8 bits but it's not required to exist. So you should use it where you are relying on the modulo-256 assignment behaviour* of an 8 bit integer and where you would prefer a compile failure to misbehaviour on obscure architectures.

uint_least8_t is required to be the smallest available unsigned integer type that can store at least 8 bits. You would use it when you want to minimise the memory use of things like large arrays.

uint_fast8_t is supposed to be the "fastest" unsigned type that can store at least 8 bits; however, it's not actually guaranteed to be the fastest for any given operation on any given processor. You would use it in processing code that performs lots of operations on the value.

The practice is that the "fast" and "least" types aren't used much.

The "least" types are only really useful if you care about portability to obscure architectures with CHAR_BIT != 8 which most people don't.

The problem with the "fast" types is that "fastest" is hard to pin down. A smaller type may mean less load on the memory/cache system but using a type that is smaller than native may require extra instructions. Furthermore which is best may change between architecture versions but implementers often want to avoid breaking ABI in such cases.

From looking at some popular implementations it seems that the definitions of uint_fastn_t are fairly arbitrary. glibc seems to define them as being at least the "native word size" of the system in question taking no account of the fact that many modern processors (especially 64-bit ones) have specific support for fast operations on items smaller than their native word size. IOS apparently defines them as equivalent to the fixed-size types. Other platforms may vary.

All in all if performance of tight code with tiny integers is your goal you should be bench-marking your code on the platforms you care about with different sized types to see what works best.

* Note that unfortunately modulo-256 assignment behaviour does not always imply modulo-256 arithmetic, thanks to C's integer promotion misfeature.

plugwash
  • 7,223
  • 1
  • 24
  • 40
  • 2
    glibc's definitions were chosen at a time when those optimizations didn't exist, and they are now baked into the ABI and cannot be changed. This is one of the several reasons why the _least and _fast types are not actually useful in practice. – zwol Jan 28 '16 at 17:19
  • 4
    @zwol: I wish the language would add types types that were defined in terms of layout and semantic requirements, e.g. "I need something whose lower bits will alias other 16-bit types, and which can hold values 0-65535, but I don't need it to peg larger values to that range". Aliasing, layout, range, and out-of-range behavior should be four separate aspects of a type, but C only allows certain combinations which aren't consistent among different platforms. – supercat Jan 28 '16 at 23:45
5

Some processors cannot operate as efficiently on smaller data types as on large ones. For example, given:

uint32_t foo(uint32_t x, uint8_t y)
{
  x+=y;
  y+=2;
  x+=y;
  y+=4;
  x+=y;
  y+=6;
  x+=y;
  return x;
}

if y were uint32_t a compiler for the ARM Cortex-M3 could simply generate

add r0,r0,r1,asl #2   ; x+=(y<<2)
add r0,r0,#12         ; x+=12
bx  lr                ; return x

but since y is uint8_t the compiler would have to instead generate:

add r0,r0,r1          ; x+=y
add r1,r1,#2          ; Compute y+2
and r1,r1,#255        ; y=(y+2) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#4          ; Compute y+4
and r1,r1,#255        ; y=(y+4) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#6          ; Compute y+6
and r1,r1,#255        ; y=(y+6) & 255
add r0,r0,r1          ; x+=y
bx  lr                ; return x

The intended purpose of the "fast" types was to allow compilers to replace smaller types which couldn't be processed efficiently with faster ones. Unfortunately, the semantics of "fast" types are rather poorly specified, which in turn leaves murky questions of whether expressions will be evaluated using signed or unsigned math.

supercat
  • 69,493
  • 7
  • 143
  • 184
  • The extra potentially unnecessary instructions when dealing with a smaller data type vs a larger data type of the native word size go a long way to illustrate why many of the "fast" data types may have a greater bit width than expected. Thanks you for your example. – Galaxy Jul 04 '18 at 20:12
  • @Galaxy: Unfortunately, the Standard does not allow for the possibility of "least" types whose behavior could vary depending upon context. On many machines, for example, arithmetic on 32-bit values in registers may be faster than operations using 8-bit values in registers, but 8-bit loads and stores would be the same speed as 32-bit loads and stores, and caching issues may cause 8-bit values to be more efficient. – supercat Jul 04 '18 at 22:04
4

1.Can you explain what is the meaning of "it's an unsigned int with at least 8 bits"?

That ought to be obvious. It means that it's an unsigned integer type, and that it's width is at least 8 bits. In effect this means that it can at least hold the numbers 0 through 255, and it can definitely not hold negative numbers, but it may be able to hold numbers higher than 255.

Obviously you should not use any of these types if you plan to store any number outside the range 0 through 255 (and you want it to be portable).

2.How uint_fast8_t and uint_least8_t help increase efficiency/code space compared to the uint8_t?

uint_fast8_t is required to be faster so you should use that if your requirement is that the code be fast. uint_least8_t on the other hand requires that there is no candidate of lesser size - so you would use that if size is the concern.


And of course you use only uint8_t when you absolutely require it to be exactly 8 bits. Using uint8_t may make the code non-portable as uint8_t is not required to exist (because such small integer type does not exist on certain platforms).

skyking
  • 12,561
  • 29
  • 47
3

The "fast" integer types are defined to be the fastest integer available with at least the amount of bits required (in your case 8).

A platform can define uint_fast8_t as uint8_t then there will be absolutely no difference in speed.

The reason is that there are platforms that are slower when not using their native word length.

LPs
  • 15,295
  • 7
  • 27
  • 56
0

I'm using the fast datatypes (uint_fast8_t) for local vars and function parameters, and using the normal ones (uint8_t) in arrays and structures which are used frequently and memory footprint is more important than the few cycles that could be saved by not having to clear or sign extend the upper bits. Works great, except with MISRA checkers. They go nuts from the fast types. The trick is that the fast types are used through derived types that can be defined differently for MISRA builds and normal ones.

I think these types are great to create portable code, that's efficient on both low-end microcontrollers and big application processors. The improvement might be not huge, or totally negligible with good compilers, but better than nothing.

vjalle
  • 1
  • 2