Question

C++ Fundamental Type Sizes: What the Standard Guarantees About int, long, char, float, and double

cppintegerlanguage-lawyerlong-integerc++-faq

Question

I want detailed information about the sizes of C++ fundamental types.

I understand that sizes can depend on the architecture (for example 16-bit, 32-bit, or 64-bit systems) and on the compiler. However, does the C++ standard define any guarantees for these types?

I am using Visual Studio 2008 on a 32-bit system, and I observe the following sizes:

char   : 1 byte
short  : 2 bytes
int    : 4 bytes
long   : 4 bytes
float  : 4 bytes
double : 8 bytes

I would like reliable information about what the C++ standard says regarding the sizes of char, short, int, long, float, double, and other built-in types, and how those sizes can vary across compilers and architectures.

Short Answer

By the end of this page, you will understand what the C++ standard actually guarantees about fundamental type sizes, what it leaves implementation-defined, why int and long do not have fixed sizes, and how to write portable C++ code when exact widths matter.

Concept

In C++, the sizes of fundamental types are not fully fixed by the language standard. Instead, the standard defines:

  • minimum guarantees
  • relative ordering rules between integer types
  • broad properties of floating-point types
  • the rule that char is always exactly 1 byte

The important idea is that in C++, a byte does not necessarily mean 8 bits. A byte is the size of char, and its number of bits is given by CHAR_BIT from <climits>.

Integer types

For the standard integer types, the language guarantees an ordering like this:

sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

More precisely, the standard guarantees that each type can represent at least a certain range:

  • signed char: at least -127 to 127
  • short: at least -32767 to 32767
  • int: at least -32767 to 32767
  • long: at least -2147483647 to 2147483647
  • long long: at least -9223372036854775807 to 9223372036854775807

This means:

  • short must be at least 16 bits
  • int must be at least 16 bits
  • long must be at least 32 bits
  • long long must be at least 64 bits

But the exact size in bytes is still implementation-defined.

For example, all of these are valid on different platforms:

  • int = 2 bytes, long = 4 bytes
  • int = 4 bytes, long = 4 bytes
  • int = 4 bytes, long = 8 bytes

Character types

char is special:

  • sizeof(char) is always 1
  • this means one byte, not necessarily 8 bits
  • signed char, unsigned char, and char are three distinct types
  • whether plain char behaves like signed or unsigned is implementation-defined

Floating-point types

The standard does not require float, double, and long double to have specific byte sizes either.

It only guarantees:

sizeof(float) <= sizeof(double) <= sizeof(long double)

In practice, common systems often use:

  • float: 4 bytes
  • double: 8 bytes
  • long double: 8, 10, 12, or 16 bytes depending on compiler and platform

Many implementations follow IEEE 754 formats, but the standard historically did not require that in all cases.

Why this matters

This design makes C++ portable across many kinds of machines. But it also means you should not assume:

  • int is always 32 bits
  • long is always larger than int
  • double always uses IEEE 754 binary64
  • a byte is always 8 bits

When your program depends on exact widths, use fixed-width integer types from <cstdint>, such as:

std::int32_t
std::uint64_t

These are the correct tools when binary layout, networking, file formats, cryptography, or hardware communication require exact sizes.

Mental Model

Think of C++ fundamental types as container labels with minimum capacity, not exact box dimensions.

  • short means: "a small integer type, at least this capable"
  • int means: "the natural integer type for the machine"
  • long means: "an integer type at least as capable as int"

The standard does not say, "This box must always be exactly 4 bytes wide." It says more like, "This box must hold at least this much, and these boxes must be ordered from no larger to no smaller."

So if you ask for an int, you are asking for a general-purpose integer type chosen by the implementation. If you ask for std::int32_t, you are asking for a box with exactly 32 bits, if the implementation provides it.

That is the key distinction:

  • language types like int are about general-purpose programming
  • fixed-width types like std::int32_t are about exact storage requirements
Take Quiz

Syntax and Examples

Checking type sizes in C++

You can inspect sizes with sizeof:

#include <iostream>

int main() {
    std::cout << "char: " << sizeof(char) << "\n";
    std::cout << "short: " << sizeof(short) << "\n";
    std::cout << "int: " << sizeof(int) << "\n";
    std::cout << "long: " << sizeof(long) << "\n";
    std::cout << "long long: " << sizeof(long long) << "\n";
    std::cout << "float: " << sizeof(float) << "\n";
    std::cout << "double: " << sizeof(double) << "\n";
    std::cout << "long double: " << sizeof(long double) << "\n";
}

sizeof returns the size in bytes, where a byte is the size of char.

Checking how many bits are in a byte

#include <iostream>
#include <climits>

int main() {
    std::cout << "Bits per byte: " << CHAR_BIT << "\n";
}

On most modern systems, CHAR_BIT is 8.

Getting exact-width integers when needed

#include <cstdint>
#include <iostream>

int main() {
    std::int32_t id = 100;
    std::uint64_t big = 10000000000ULL;

    std::cout << sizeof(id) << "\n";
    std::cout << sizeof(big) << "\n";
}

This is useful when the program must store values with known widths.

Example: portable assumptions

#include <iostream>
#include <limits>

int main() {
    std::cout << "int bytes: " << sizeof(int) << "\n";
    std::cout << "int max: " << std::numeric_limits<int>::max() << "\n";
}

Instead of guessing the size of int, ask the implementation directly.

Typical data models

These are common, but not guaranteed by the standard:

Modelintlonglong longpointers
ILP324484
LP644888
LLP644488

Examples:

  • many 32-bit systems use ILP32
  • many Unix-like 64-bit systems use LP64
  • 64-bit Windows commonly uses LLP64

Step by Step Execution

Example program

#include <iostream>
#include <climits>

int main() {
    std::cout << sizeof(char) << "\n";
    std::cout << sizeof(int) << "\n";
    std::cout << CHAR_BIT << "\n";
}

What happens step by step

  1. #include <iostream> makes std::cout available.
  2. #include <climits> makes constants like CHAR_BIT available.
  3. The program starts in main().
  4. sizeof(char) is evaluated.
    • This is always 1 by definition in C++.
    • It means one byte.
  5. sizeof(int) is evaluated.
    • The compiler provides the actual size for the current platform.
    • On many systems, this is 4, but the standard does not require that.
  6. CHAR_BIT is printed.
    • This tells you how many bits are in one byte on this implementation.
    • Often this is 8.

Example interpretation

If the output is:

1
4
8

that means:

  • char is 1 byte
  • int is 4 bytes
  • each byte has 8 bits

So on that machine, int is 32 bits.

Important detail

This conclusion comes from:

sizeof(int) * CHAR_BIT

That is the portable way to think about bit width.

Real World Use Cases

Binary file formats

If you read or write binary files, exact widths matter. A file format may require a 32-bit integer or a 16-bit field. In that case, use fixed-width types like std::uint32_t.

Network protocols

Protocols often define packet fields with exact sizes. Assuming int is 32 bits can break communication on other systems.

Embedded systems

On small devices, int may not match desktop expectations. Code that assumes desktop sizes may fail or waste memory.

Cross-platform libraries

Portable libraries avoid hardcoding assumptions about long and int. They use standard limits, sizeof, and fixed-width types where needed.

Memory-sensitive applications

Sometimes developers choose smaller types to reduce memory usage in large arrays or data structures. To do that safely, they must know what the implementation guarantees.

Serialization and deserialization

When converting objects to bytes for storage or transmission, exact widths and layout rules become important. Using plain int in serialized formats is often a portability bug.

Take Quiz

Real Codebase Usage

In real projects, developers usually follow a few practical rules.

1. Use int for ordinary arithmetic and loop counters

for (int i = 0; i < count; ++i) {
    // work
}

Why? Because int is usually the implementation's natural general-purpose integer type.

2. Use fixed-width types for external data

#include <cstdint>

struct PacketHeader {
    std::uint32_t length;
    std::uint16_t type;
};

This is common in networking, storage, and APIs.

3. Use std::size_t for object sizes and indexing

std::size_t n = values.size();

This is the correct type for sizes returned by the standard library.

4. Use guard checks instead of assumptions

static_assert(sizeof(std::uint32_t) == 4, "Expected 32-bit uint32_t");

This helps catch environment mismatches at compile time.

5. Query limits instead of guessing

#include <limits>

int maxValue = std::numeric_limits<int>::max();

This avoids hardcoded assumptions.

6. Be careful with long

A common real-world rule is: do not use long when you mean "64-bit integer". On some platforms it is 8 bytes, on others it is 4.

Use:

  • long only when platform-dependent size is acceptable
  • std::int64_t when exactly 64 bits are required

Common Mistakes

Mistake 1: Assuming int is always 32 bits

Broken example:

int userId = 2147483647;

This value fits on common systems, but the assumption that int is always 32 bits is not guaranteed by the standard.

Avoid it by using:

std::int32_t userId = 2147483647;

when exact width matters.

Mistake 2: Assuming long is always bigger than int

Broken assumption:

// Not necessarily larger than int on every system
long value = 10;

long is guaranteed to be at least as wide as int, but it may be the same size.

Mistake 3: Treating a byte as always 8 bits

Broken assumption:

int bits = sizeof(int) * 8;

Portable version:

#include <climits>
int bits = sizeof(int) * CHAR_BIT;

Mistake 4: Using sizeof to infer value range directly without considering signedness

Broken thinking:

  • same number of bytes means same numeric range

But signed and unsigned types differ in range.

unsigned int a = 4000000000U;
// This may fit even when signed int cannot represent the same value.

Mistake 5: Using long in file or network formats

Broken example:

struct Record {
    long id;
};

This can break across platforms because long changes size.

Better:

#include <cstdint>

struct Record {
    std::int32_t id;
};

Mistake 6: Forgetting that char, signed char, and unsigned char are different types

These are distinct types in C++. Plain char is not just another spelling of signed char.

Mistake 7: Assuming floating-point sizes are fixed everywhere

Common systems use 4-byte float and 8-byte double, but you should not write code that depends on that without checking the implementation or using the appropriate numeric limits.

Comparisons

General-purpose types vs exact-width types

Type choiceWhat it meansPortable for exact storage?Common use
intNatural integer typeNoGeneral arithmetic
longAt least as wide as intNoPlatform-dependent integer work
long longAt least 64 bitsNot exact width, but minimum guaranteedLarge integers
std::int32_tExactly 32 bitsYes, if providedFile formats, protocols
std::uint64_tExactly 64 bitsYes, if providedLarge unsigned binary data
std::size_tType for sizes and countsNot fixed widthContainer sizes, indexing

int vs long

Featureintlong
Minimum widthAt least 16 bitsAt least 32 bits
Must be larger than the other?NoNo, may be same size as int
Best forGeneral-purpose integersLarger platform-dependent integers

float vs double

Featurefloatdouble
Relative sizeNo larger than doubleAt least as large as float
Typical size4 bytes8 bytes
PrecisionLowerHigher
Use caseMemory-sensitive numeric dataDefault floating-point choice in many programs

Common platform models

Platform styleintlongNotes
32-bit Windows44Matches your Visual Studio example
64-bit Windows44Uses LLP64
64-bit Linux/macOS48Uses LP64
Take Quiz

Cheat Sheet

Core guarantees

  • sizeof(char) == 1
  • sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
  • sizeof(float) <= sizeof(double) <= sizeof(long double)
  • a byte is the size of char
  • bits per byte are given by CHAR_BIT

Minimum integer widths

  • short: at least 16 bits
  • int: at least 16 bits
  • long: at least 32 bits
  • long long: at least 64 bits

Important reminders

  • int is not guaranteed to be 32 bits
  • long is not guaranteed to be 64 bits
  • char is always 1 byte, but a byte is not guaranteed to be 8 bits
  • char, signed char, and unsigned char are distinct types

Useful headers

#include <climits>   // CHAR_BIT, INT_MAX, LONG_MAX
#include <cstdint>   // std::int32_t, std::uint64_t
#include <limits>    // std::numeric_limits<T>

Useful expressions

sizeof(int)               // size in bytes
sizeof(int) * CHAR_BIT    // size in bits
std::numeric_limits<int>::max()

When to use what

  • use int for ordinary counting and arithmetic
  • use std::size_t for sizes and indexes
  • use std::int32_t / std::uint64_t when exact width matters
  • avoid using long to mean "64-bit integer"

Typical but not guaranteed sizes

  • char: 1 byte
  • short: 2 bytes
  • int: 4 bytes
  • long: 4 or 8 bytes
  • long long: 8 bytes
  • float: 4 bytes
  • double: 8 bytes

FAQ

Is int always 4 bytes in C++?

No. The standard only guarantees that int is at least 16 bits. Many modern systems use 4-byte int, but not all.

Is long always 8 bytes on 64-bit systems?

No. On 64-bit Linux and macOS, long is often 8 bytes. On 64-bit Windows, long is usually 4 bytes.

Why is sizeof(char) always 1?

Because C++ defines a byte as the size of char. That does not mean a byte must contain 8 bits.

How can I get an exact 32-bit integer in C++?

Use std::int32_t from <cstdint>.

How do I check type limits portably?

Use std::numeric_limits<T> from <limits> or constants such as INT_MAX from <climits>.

Is double always 8 bytes?

Often yes in practice, but the standard does not require that exact size on every implementation.

Should I use long for file formats or network messages?

Usually no. Use fixed-width types like std::uint32_t or std::int64_t so the layout is predictable.

What type should I use for container sizes?

Use std::size_t, because that is the standard type for sizes and indexes.

Take Quiz

Mini Project

Description

Build a small C++ program that prints the sizes and limits of fundamental numeric types on your machine. This demonstrates the difference between what the language guarantees and what your compiler actually chooses for the current platform.

Goal

Create a tool that reports type sizes, bit widths, and numeric limits in a portable way.

Requirements

  • Print the size of char, short, int, long, long long, float, double, and long double.
  • Print the number of bits in a byte using CHAR_BIT.
  • Print the minimum and maximum values for at least int and long.
  • Show the size of one fixed-width type such as std::int32_t.
  • Use standard library headers only.
Take Quiz

Keep learning

Related questions

View more