Lesson 2 of 8
Lesson 3.1 ended on an uncomfortable note: int is usually 4 bytes, but the standard doesn't
promise it. Most of the time that's fine. Sometimes it very much isn't.
When the exact size matters
Three situations, all of which you will eventually hit:
- Binary file formats. A PNG header says a field is exactly 4 bytes. If your
intis 2 or 8, you read garbage. - Network protocols. Same problem — the bytes on the wire have a fixed layout agreed by both ends.
- Hardware registers. An embedded device's control register is exactly 8 or 16 bits wide.
In all three the size is part of the contract, and "whatever your compiler felt like" is not an acceptable answer.
The <cstdint> types
#include <cstdint>
#include <iostream>
int main() {
std::int8_t a = 100;
std::int16_t b = 30000;
std::int32_t c = 2000000000;
std::int64_t d = 9000000000000000000;
std::uint8_t e = 200;
std::uint32_t f = 4000000000;
std::cout << sizeof(a) << " " << sizeof(b) << " "
<< sizeof(c) << " " << sizeof(d) << "\n";
std::cout << sizeof(e) << " " << sizeof(f) << "\n";
std::cout << b << " " << c << " " << d << "\n";
std::cout << f << "\n";
return 0;
}
1 2 4 8
1 4
30000 2000000000 9000000000000000000
4000000000
The naming is completely systematic:
intoruint— signed (can be negative) or unsigned (zero and up).- the number — how many bits, so
int32_tis 4 bytes. _t— a convention meaning "this is a type name."
These live in <cstdint> and, like everything else in the standard library, in namespace std.
The int8_t trap
There's one wrinkle that catches everyone once:
#include <cstdint>
#include <iostream>
int main() {
std::int8_t small = 65;
std::cout << small << "\n";
std::cout << static_cast<int>(small) << "\n";
return 0;
}
A
65
int8_t is almost always a typedef for signed char — and streams print char types as
characters, not numbers. 65 is the character code for A (lesson 3.5).
If you want to see the number, convert it: static_cast<int>(small). This bites people printing
byte values while debugging, and the fix is always the same.
So which should you use?
Reach for int by default. It's the type the language and every library is built around,
it's the size the processor handles most naturally, and using int32_t everywhere just adds
noise without adding safety.
Reach for <cstdint> types when the size is genuinely part of the problem: file formats,
network packets, hardware, or when you're deliberately packing memory and every byte counts.
The rule of thumb: if you can't articulate why you need exactly 32 bits, you want int.
A related type: std::size_t
You'll see this constantly, because it's what the standard library uses for sizes and counts:
#include <iostream>
#include <string>
int main() {
std::string name = "Evil Genius";
std::size_t length = name.size();
std::cout << length << "\n";
std::cout << sizeof(std::size_t) << "\n";
return 0;
}
11
8
std::size_t is an unsigned integer type big enough to hold the size of any object your
system can create — 8 bytes on a 64-bit machine. Every container's .size() returns one.
Being unsigned is exactly where it gets dangerous, and that's the whole of the next lesson.
Try it
Print the size in bytes of int, long, std::size_t, std::int16_t, and std::int64_t on
your machine. Which of these are guaranteed by the standard, and which just happen to be that
size on your computer?
Checkpoint
<cstdint>gives types with a guaranteed bit width:int8_t…int64_tanduintversions.- Use them when size is part of the contract — file formats, protocols, hardware.
- Use plain
intfor ordinary counting; it's what the language is built around. int8_tanduint8_tprint as characters —static_cast<int>to see the number.std::size_tis the unsigned type the standard library uses for all sizes and counts.
Questions about this lesson
Log in to ask a question.