C - Type - What are uint8_t, uint16_t, uint32_t and uint64_t?

You are likely wondering what are uint8_t, uint16_t, uint32_t and uint64_t.
That's a good question. Because it could be really helpul!

It turns out that they are equal respectively to: unsigned char, unsigned short, unsigned int and unsigned long long.
But what are ranges of all these types?

Let's test it in this C type tutorial.

Explanation

We're going to use a variable called testValue equal to 0xFFFFFFFFFFFFFFFF.

Notice that 0xFFFFFFFFFFFFFFFF is the same as 18,446,744,073,709,551,615 and this is the maximum value possible for an unsigned long long, depending on your processor architecture (as gineera said in its comment).

It's not so easy to understand all these things, but keep trying, it will be clearer after the end of this tutorial. At least, I hope it.

In the code part we will see that the number8 variable has a result of 255.
Why? Because 255 is the maximum value of an unsigned char or an uint8_t.
So if we put a value of 256, our result would be 0.
Indeed, after 255 we go back to 0.

For example if we added +1 for each number below, we'd have:

0 (First value of a char or 1 byte)
1
2
3
...
250
251
252
253
254
255 (Max value of a char or 1 byte)
0 (First value of a char or 1 byte)
1
2
3
... until 255 (Max value of a char or 1 byte)
0
1
2
3
... until 255 (Max value of a char or 1 byte)
0
1
2
3
and so on.

So we won't be able to have a value of 256 in a char (or a byte).
If we wanted to have a such value, we would have to use another type, for example an unsigned short or an uint16_t equal to 2 bytes or 16 bits.

Wow, this is still confuse? Let's continue!

Indeed, with an unsigned short, we will be able to use this type up a value of 65535 in decimal
or 0xFFFF in hex.

But in our example, we're going to use a huge value: 18,446,744,073,709,551,615.

And what we'll have will be the max value of each type!

Because this huge value is the maximum value of an unsigned long long.
So every type is set at the maximum value because they are a multiple of each maximum.

2^2 = 4
2^4 = 16
2^8 = 256
2^16 = 65536
2^32 = 4294967296
2^64 = 18446744073709551616

OK, but why the maximum value of a byte is 255 and not 256?
Because (2^8) - 1 = 256 - 1 = 255.
Indeed, our first value is 0 and not 1.
So the second is 1, the third is 2, and so on.
Thus, our last value is 255.

Code

// testValue
unsigned long long testValue     = 0xFFFFFFFFFFFFFFFF; // 18446744073709551615

// 1 byte -> [0-255] or [0x00-0xFF]
uint8_t         number8     = testValue; // 255
unsigned char    numberChar    = testValue; // 255

// 2 bytes -> [0-65535] or [0x0000-0xFFFF]
uint16_t         number16     = testValue; // 65535
unsigned short    numberShort    = testValue; // 65535

// 4 bytes -> [0-4294967295] or [0x00000000-0xFFFFFFFF]
uint32_t         number32     = testValue; // 4294967295
unsigned int     numberInt    = testValue; // 4294967295

 // 8 bytes -> [0-18446744073709551615] or [0x0000000000000000-0xFFFFFFFFFFFFFFFF]
uint64_t             number64         = testValue; // 18446744073709551615
unsigned long long     numberLongLong    = testValue; // 18446744073709551615

Conclusion

Now you are able to handle bits and bytes like a professional.
Well done, you've made it. cool

Comments

Comment: 

Note that the 'fixed-size' types (int16_t etc) are NOT always directly equivalent to the standard C types given above (short etc) - it depends on the processor platform and compiler - that is why the fixed types were more recently introduced.

On a desktop 32-bit PC an int would be 32-bits; on an 8-bit micro both int and short are normally 16-bit. If the data size is critical (eg a set of bit flags) always use the fixed-size types.

Comment: 

They're not even necessarily equivalent to *any* of the "standard C types", since the u?intN_t types must be exactly the number of bits as stated, with no padding bits. The signed types (intN_t) must also use two's complement representation. The normal integer types have no such guarantees.

Comment: 

Dear Gineera,

You're absolutely right!

Indeed, for example on a 16-bit microprocessor we have as maximum values:

  • int = 32767                                     -> 1 more = -32768 ; 2 more = -32767
  • unsigned int = 65535                   -> 1 more = 0 ; 2 more = 1
  • long = 2147483647                      -> 1 more = -2147483648 ; 2 more = -2147483647
  • unsigned long = 4294967296    -> 1 more = 0 ; 2 more = 1

If you had an int with 32767 as value and added just 1, it would become -32768 because after 32767 we go back to the first value of an int, in our case -32768.

Each +1 added will go from -32768 to 0 and 0 to 32767.

It's not the case for an unsigned int because it starts from 0 and there is so no negative value. So the max is 65535 and if we had +1, we would go back to the first value, 0!

Thank you for your comment. angel

Comment: 

thanks for important info.

Comment: 

hi
thanks! :)

Comment: 

Dude, I don't know why, but it's so hard to find someone who can actually explain the C int types in such a simple way.

From a C noob, thanks man. :)

Comment: 

thanks!! so well explained...

Comment: 

Thank you for the explanation and illustration.

Comment: 

A clear explanation but it might help to add that each of those F's above is actually 1111 in your computer, so that 255 is 11111111 and testValue is 64 1's. For a full lesson google "counting in binary" but the short answer is 11111111 rolls over to 00000000 just as 99 would become 00 if a calculator only had two digits.
Finally, to ensure the most compact and fastest code, always use the smallest representation you can. So, if a counter can only go to 85 (say) then uint8_t is appropriate. Of course, you never want a uint8_t to inadvertently roll over to zero as you'll code will break for sure.

Comment: 

The fastest code is often aligned code, so using the smallest sizes for things is not always the best way. Also padding in structs and classes when using different sizes can bloat your types, so it does not mean using the smallest sized attributes will be translated to your assembly. Making sure you organize diff size atts in your header files AND using min size types is a good thing. :-)

Comment: 

Thank you. :]

Comment: 

Thank you very much!

Comment: 

Thanking your explanation about numbering concepts, so very useful this concepts to every one

Comment: 

thank you for your explanation. i try to search about it in many time and find your explanation. it's so usefull. thank you very much :')

Comment: 

Sir what is the meaning of last char ('t') in 'uint16_t ' or in 'uint8_t'.

Comment: 

Hello,

"_t" stands for "type" or made from a typedef.

I think it's like that by convention. smiley

 

Comment: 

Thank You!!! Great reading but the "t" was bothering me!!! :-)

Comment: 

Hi sir ,
nice topic you explain please can you explain uintptr and int *ptr difference.

Comment: 

Hello sunil,

The main difference between:

  • uint *ptr
  • int *ptr

is that "uint" is an unsigned integer.

It means that this is a number from 0 to +4,294,967,295.

Where as a "int" is from -2,147,483,648 to +2,147,483,647.

The first cannot be a negative one, the second can.

So when you use "uint *ptr" means that your pointer is pointing on a number from zero to +4,294,967,295.

And for "int *ptr", it means that your pointer is an adress where the number is potentially a negative number, from -2,147,483,648 to +2,147,483,647.

To resume, the value of the integer in the first example is positive, the second is maybe negative.

The main goal of using a "unsigned int" is that you can store a value until 4 billion with no problem, where in the second example you can store "only" a number of 2 billion.

I hope it helps. laugh

Comment: 

I think +2,147,483,648 should be +2,147,483,647

Comment: 

Thank you jlong29,

I corrected it. cool

Comment: 

But pointer always contain address and how address can be a negative value ?

Comment: 

Hello omkar,

A pointer is an address.

At this address there is a value.

This value can be negative. wink

Comment: 

While performing binary operations like addition or subtraction on uint16_t or uint32_t data types, does the execution happen bitwise in C?

For example, if I want to add 0xFF with 0x11, is it first converted into the form 11111111 + 00010001 and then added using the binary addition rules or is it done like simple addition of integers?

Comment: 

Hello Maria,

I think it's the same as an addition or a subtraction.

Because in programming everything is, at the end, converted in binary. laugh

Comment: 

thanks for the info

Comment: 

The choice of int for most compilers is defined by the data bus size of the target processor. This is why you will find that:

char 8 (int on an 8 bit processor)
short 16 (int on a 16 bit processor)
long 32 (int on a 32 bit processor)
long long 64 (int on a 64 bit processor)

And the size of a pointer will match the largest type that will fit the size of the address bus

What seems to be missing from this article is the place in C libraries where these typedefs are usually located...

Comment: 

sir can u please tell how can we print any array or any variable defined by uint8_t data type

Comment: 

Hello meena,

To display a uint8_t do the same as a int. angel

Comment: 

Hey!
Like your way of explaining :D so human and caring <3

Comment: 

Write the declaration of variable x that is an array of pointers to n memory blocks, where each memory block contains a 16-bit signed integer value.
given: const size_t n = 50;

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.