Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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 int is 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:

  • int or uint — signed (can be negative) or unsigned (zero and up).
  • the number — how many bits, so int32_t is 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.

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_t and uint versions.
  • Use them when size is part of the contract — file formats, protocols, hardware.
  • Use plain int for ordinary counting; it's what the language is built around.
  • int8_t and uint8_t print as characters — static_cast<int> to see the number.
  • std::size_t is the unsigned type the standard library uses for all sizes and counts.

Questions about this lesson

Log in to ask a question.