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.


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)
255 (Max value of a char or 1 byte)
0 (First value of a char or 1 byte)
... until 255 (Max value of a char or 1 byte)
... until 255 (Max value of a char or 1 byte)
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.


// 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


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



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.


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. laugh


thanks for important info.


thanks! :)


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. :)


thanks!! so well explained...


Thank you for the explanation and illustration.


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.


Thank you. :]


Thank you very much!


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


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 :')


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



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

I think it's like that by convention. 



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


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


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. :)




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


Thank you jlong29,

I corrected it. laugh


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


Hello omkar,

A pointer is an address.

At this address there is a value.

This value can be negative.



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?


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



thanks for the info

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Refresh Type the characters you see in this picture. Type the characters you see in the picture; if you can't read them, submit the form and a new image will be generated. Not case sensitive.  Switch to audio verification.