DEV Community

estrolabz
estrolabz

Posted on

The Ultimate Guide to C++ Data Types: Usage Rules, Internals, and Complete Reference

Introduction

This article will cover everything a C++ beginner will ever need to know along with some useful deep dive educational information for some advanced C++ developers. In this article we will cover most C++ data types along with detailed information about the inner workings of this and even some useful checklists for you to know when to use each data type vs another. But that is not all, do you need a reference table of summary data type information well that is also included in this article.


Summary

General Data Types

Data Type Description Unsigned / Signed Size (Bits) Fixed / Dynamic Min / Max Range
bool Boolean value (True / 1, False / 0) N/A 8 bits Fixed 0 - 1
char Single ASCII character like 'a' Unsigned & Signed 8 bits Fixed Signed (-128 to 127), Unsigned (0 to 255)
short Smaller integer type Unsigned & Signed Usually 16 bits Dynamic (Platform Dependent) Signed (-32,768 to 32,767), Unsigned (0 to 65,535)
int Standard integer Unsigned & Signed Usually 32 bits Dynamic (Platform Dependent) Signed (-2,147,483,648 to 2,147,483,647), Unsigned (0 to 4,294,967,295)
long Larger whole number Unsigned & Signed 64 bits (on 64-bit OS), 32 bits (on 32-bit OS) Dynamic (Platform Dependent) Signed 32-bit (-2,147,483,648 to 2,147,483,647), Unsigned 32-bit (0 to 4,294,967,295),
Signed 64-bit (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), Unsigned 64-bit (0 to 18,446,744,073,709,551,615)
long long Even larger whole number Unsigned & Signed Usually 64 bits Dynamic (Platform Dependent) Signed (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), Unsigned (0 to 18,446,744,073,709,551,615)
float Single precision decimal number Signed 32 bits Fixed Approx. ±3,402,823,466.3852886
double Double precision decimal number Signed 64 bits Fixed Approx. ±1.7976931348623157 × 10³⁰⁸
long double (GCC x86/x64) Extended precision float on GCC Linux Signed Usually 80 bits Fixed Approx. ±1.1897314953572317 × 10⁴⁹³²
long double (Clang macOS) Extended precision float on macOS Signed 64 bits Fixed Approx. ±1.7976931348623157 × 10³⁰⁸
long double (GCC PowerPC) Extended precision float on PPC Signed 128 bits Fixed Approx. ±1.1897314953572317 × 10⁴⁹³²
long double (MSVC / Intel) Treated like double Signed 64 bits Fixed Approx. ±1.7976931348623157 × 10³⁰⁸
wchar_t Wide character (GCC Linux) Unsigned 32 bits Fixed 0 to 4,294,967,295
char16_t UTF-16 character Unsigned 16 bits Fixed 0 to 65,535
char32_t UTF-32 character Unsigned 32 bits Fixed 0 to 4,294,967,295
void No value / function return type N/A N/A N/A N/A

Fixed Width Integer Types

Data Type Description Signed / Unsigned Size Min / Max Range
int8_t Signed 8-bit integer Signed 8 bits -128 to 127
uint8_t Unsigned 8-bit integer Unsigned 8 bits 0 to 255
int16_t Signed 16-bit integer Signed 16 bits -32,768 to 32,767
uint16_t Unsigned 16-bit integer Unsigned 16 bits 0 to 65,535
int32_t Signed 32-bit integer Signed 32 bits -2,147,483,648 to 2,147,483,647
uint32_t Unsigned 32-bit integer Unsigned 32 bits 0 to 4,294,967,295
int64_t Signed 64-bit integer Signed 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
uint64_t Unsigned 64-bit integer Unsigned 64 bits 0 to 18,446,744,073,709,551,615
intptr_t Signed integer to store a pointer Signed 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
uintptr_t Unsigned pointer-sized integer Unsigned 64 bits 0 to 18,446,744,073,709,551,615
intmax_t Largest available signed integer Signed 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
uintmax_t Largest available unsigned integer Unsigned 64 bits 0 to 18,446,744,073,709,551,615
int_least8_t Smallest type ≥8 bits (signed) Signed 8 bits -128 to 127
int_least16_t Smallest type ≥16 bits (signed) Signed 16 bits -32,768 to 32,767
int_least32_t Smallest type ≥32 bits (signed) Signed 32 bits -2,147,483,648 to 2,147,483,647
int_least64_t Smallest type ≥64 bits (signed) Signed 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
int_fast8_t Fastest type ≥8 bits (signed) Signed 32 bits* -2,147,483,648 to 2,147,483,647 (platform-dependent)
int_fast16_t Fastest type ≥16 bits (signed) Signed 32 bits* -2,147,483,648 to 2,147,483,647 (platform-dependent)
int_fast32_t Fastest type ≥32 bits (signed) Signed 32 bits -2,147,483,648 to 2,147,483,647
int_fast64_t Fastest type ≥64 bits (signed) Signed 64 bits -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Inner Workings

Signed VS Unsigned

What Do “Signed” and “Unsigned” Mean?

  • Signed integers can represent both negative and positive numbers.
  • Unsigned integers can represent only non-negative numbers (zero and positive values), but with double the positive range.

Every integer type in C++ (e.g., char, short, int, long) can be signed or unsigned:

Type Signed Range Unsigned Range
int −2,147,483,648 to 2,147,483,647 0 to 4,294,967,295
short −32,768 to 32,767 0 to 65,535
char −128 to 127 or 0 to 255 (impl. defined)

Why Use Unsigned?

  • When you never need negative numbers (e.g., sizes, counts, flags)
  • To increase the positive range without using a larger type
  • To represent raw binary data safely (uint8_t, unsigned char, etc.)

Important Behavior Differences

1. Signed Overflow = Undefined Behavior

int a = INT_MAX;
a += 1;  // ❌ Undefined behavior!

Enter fullscreen mode Exit fullscreen mode

2. Unsigned Overflow = Well-defined (Wraparound)

unsigned int b = UINT_MAX;
b += 1;  // ✅ Becomes 0 (wraps around)

Enter fullscreen mode Exit fullscreen mode

3. Mixed Arithmetic Can Be Dangerous

unsigned int u = 1;
int s = -2;

if (s < u)  // ❌ Might not work as expected!

Enter fullscreen mode Exit fullscreen mode

⚠️ Mixing signed and unsigned values in arithmetic or comparisons can lead to unexpected results due to type promotion rules.


Best Practices

Situation Recommendation
Array indices, sizes ✅ Use size_t (unsigned)
General-purpose arithmetic ✅ Use int, long, etc.
Binary buffers, raw bytes ✅ Use uint8_t, unsigned char
Avoiding overflows ✅ Be explicit with types
Portability and clarity ✅ Avoid mixing signed/unsigned types

Rule of Thumb

Use signed types by default unless there’s a clear reason to use unsigned.

Use unsigned only when negative values are truly invalid, and always guard against wraparound.

What Does Fixed-Width Mean?

  • Fixed-width data types have a guaranteed, exact size in bits across all platforms.
  • Examples:
    • int8_t (8 bits)
    • uint16_t (16 bits)
    • int32_t (32 bits)
    • uint64_t (64 bits)

These come from the <cstdint> header and ensure predictable memory usage and range regardless of architecture.


What Does Dynamic-Width Mean?

  • Dynamic-width (or implementation-defined width) types have a size that can vary depending on the compiler and platform.
  • Examples:
    • int (commonly 32 bits on modern platforms but not guaranteed)
    • long (typically 32 or 64 bits depending on OS)
    • char (always 8 bits but signedness varies)

Their size and range depend on hardware and compiler design.


Comparison Table

Aspect Fixed-Width Types Dynamic-Width Types
Size Guarantee Exact number of bits (e.g., 8, 16, 32, 64) Varies by platform/compiler
Header <cstdint> Built-in fundamental types
Portability High (ideal for cross-platform work) Medium to low (platform dependent)
Typical Use Cases Network protocols, binary files, embedded systems General programming, system APIs
Examples int32_t, uint64_t int, long, short

When to Use Each

Scenario Recommended Type
Cross-platform binary compatibility Fixed-width (int32_t, etc.)
Performance-sensitive code with known hardware Dynamic-width (int, long)
Interfacing with OS or libraries Match system types (often dynamic)
Low-level bit manipulation or protocol parsing Fixed-width for precision

Why Does This Matter?

  • Using fixed-width types avoids bugs caused by size or range differences across platforms.
  • Using dynamic-width types can leverage platform-optimized native sizes but risks portability issues.
  • For networking, file formats, and embedded systems, fixed-width types are almost always preferred.

Example

#include <cstdint>

int32_t fixedInt = 123456789;  // Always 32 bits
int dynamicInt = 123456789;    // Size varies by platform (usually 32 bits on desktop)

Enter fullscreen mode Exit fullscreen mode

Summary

  • Fixed-width: Exact, predictable sizes, great for portability and binary data.

- Dynamic-width: Compiler-dependent, convenient for general-purpose programming.

What Does Dynamic-Width Mean?

  • Dynamic-width (or implementation-defined width) types have a size that can vary depending on the compiler and platform.
  • Examples:
    • int (commonly 32 bits on modern platforms but not guaranteed)
    • long (typically 32 or 64 bits depending on OS)
    • char (always 8 bits but signedness varies)

Their size and range depend on hardware and compiler design.


Comparison Table

Aspect Fixed-Width Types Dynamic-Width Types
Size Guarantee Exact number of bits (e.g., 8, 16, 32, 64) Varies by platform/compiler
Header <cstdint> Built-in fundamental types
Portability High (ideal for cross-platform work) Medium to low (platform dependent)
Typical Use Cases Network protocols, binary files, embedded systems General programming, system APIs
Examples int32_t, uint64_t int, long, short

When to Use Each

Scenario Recommended Type
Cross-platform binary compatibility Fixed-width (int32_t, etc.)
Performance-sensitive code with known hardware Dynamic-width (int, long)
Interfacing with OS or libraries Match system types (often dynamic)
Low-level bit manipulation or protocol parsing Fixed-width for precision

Why Does This Matter?

  • Using fixed-width types avoids bugs caused by size or range differences across platforms.
  • Using dynamic-width types can leverage platform-optimized native sizes but risks portability issues.
  • For networking, file formats, and embedded systems, fixed-width types are almost always preferred.

Example

#include <cstdint>

int32_t fixedInt = 123456789;  // Always 32 bits
int dynamicInt = 123456789;    // Size varies by platform (usually 32 bits on desktop)

Enter fullscreen mode Exit fullscreen mode

Summary

  • Fixed-width: Exact, predictable sizes, great for portability and binary data.

- Dynamic-width: Compiler-dependent, convenient for general-purpose programming.

Min Bits vs Speed Fixed Data Type Requirements

Understanding int_leastN_t vs int_fastN_t

When writing portable and efficient C++ code using fixed-width integers, you may come across two special families of types defined in <cstdint>:

  • int_leastN_t / uint_leastN_t
  • int_fastN_t / uint_fastN_t

These are designed to give you more control over performance or memory usage than plain types like int8_t.


int_leastN_tLeast Width, Guaranteed Minimum Size

Purpose:

Use the smallest integer type that is at least N bits wide.

Example:

int_least16_t x = 1000; // At least 16 bits, but possibly wider

Enter fullscreen mode Exit fullscreen mode

When to use it:

  • Memory-constrained systems (e.g., microcontrollers)
  • You don’t care about performance, just minimal size and compatibility

Guarantee:

It will never be narrower than N bits, but could be wider depending on the platform.


int_fastN_tFastest Type with Minimum Width

Purpose:

Use the fastest integer type that is at least N bits wide.

Example:

int_fast16_t y = 1000; // May be 32-bit or even 64-bit if it's faster

Enter fullscreen mode Exit fullscreen mode

When to use it:

  • Performance-critical code
  • Tight loops, counters, array indexing
  • You don’t care about memory size, just speed

Guarantee:

Like int_leastN_t, it’s at least N bits, but chosen by the compiler for best performance.


Quick Comparison

Feature int_leastN_t int_fastN_t
Minimum Size Yes Yes
Memory Efficient ✅ Best ❌ Not always
Performance Focused ❌ No ✅ Yes
Platform Variance Low High (optimized by compiler)
Use Case Portability & Size Speed & Performance

Why These Exist

C++ runs on many different hardware architectures. What’s fast and optimal on one platform might be inefficient on another. These types:

  • Help write portable, optimized, and safe code.
  • Let the compiler decide what’s best, based on your priority: speed or size.

Tip

If you need exact bit-width, use intN_t (e.g. int8_t, int64_t).

If you're writing generic code, and care about size or speed more than exact bits, use int_leastN_t or int_fastN_t.


Min Bits vs Speed Fixed Data Type Requirements

Understanding int_leastN_t vs int_fastN_t

When writing portable and efficient C++ code using fixed-width integers, you may come across two special families of types defined in <cstdint>:

  • int_leastN_t / uint_leastN_t
  • int_fastN_t / uint_fastN_t

These are designed to give you more control over performance or memory usage than plain types like int8_t.


int_leastN_tLeast Width, Guaranteed Minimum Size

Purpose:

Use the smallest integer type that is at least N bits wide.

Example:

int_least16_t x = 1000; // At least 16 bits, but possibly wider

Enter fullscreen mode Exit fullscreen mode

When to use it:

  • Memory-constrained systems (e.g., microcontrollers)
  • You don’t care about performance, just minimal size and compatibility

Guarantee:

It will never be narrower than N bits, but could be wider depending on the platform.


int_fastN_tFastest Type with Minimum Width

Purpose:

Use the fastest integer type that is at least N bits wide.

Example:

int_fast16_t y = 1000; // May be 32-bit or even 64-bit if it's faster

Enter fullscreen mode Exit fullscreen mode

When to use it:

  • Performance-critical code
  • Tight loops, counters, array indexing
  • You don’t care about memory size, just speed

Guarantee:

Like int_leastN_t, it’s at least N bits, but chosen by the compiler for best performance.


Quick Comparison

Feature int_leastN_t int_fastN_t
Minimum Size Yes Yes
Memory Efficient ✅ Best ❌ Not always
Performance Focused ❌ No ✅ Yes
Platform Variance Low High (optimized by compiler)
Use Case Portability & Size Speed & Performance

Why These Exist

C++ runs on many different hardware architectures. What’s fast and optimal on one platform might be inefficient on another. These types:

  • Help write portable, optimized, and safe code.
  • Let the compiler decide what’s best, based on your priority: speed or size.

Tip

If you need exact bit-width, use intN_t (e.g. int8_t, int64_t).

If you're writing generic code, and care about size or speed more than exact bits, use int_leastN_t or int_fastN_t.


Usage

Syntax

#include <cstdint>   // for fixed-width and related types
#include <cstddef>   // for intptr_t, uintptr_t
#include <iostream>

int main() {
    // Fixed-width integer types
    int8_t       a_int8 = -100;
    uint8_t      a_uint8 = 200;
    int16_t      a_int16 = -30000;
    uint16_t     a_uint16 = 60000;
    int32_t      a_int32 = -2000000000;
    uint32_t     a_uint32 = 4000000000U;
    int64_t      a_int64 = -9000000000000000000LL;
    uint64_t     a_uint64 = 18000000000000000000ULL;

    // Pointer-sized integers
    intptr_t     a_intptr = 0;
    uintptr_t    a_uintptr = 0;

    // Maximum-width integers
    intmax_t     a_intmax = -9223372036854775807LL;
    uintmax_t    a_uintmax = 18446744073709551615ULL;

    // Least-width integers
    int_least8_t   a_least8 = -120;
    int_least16_t  a_least16 = -32000;
    int_least32_t  a_least32 = -2000000000;
    int_least64_t  a_least64 = -9000000000000000000LL;

    // Fastest minimum-width integers
    int_fast8_t    a_fast8 = -120;
    int_fast16_t   a_fast16 = -32000;
    int_fast32_t   a_fast32 = -2000000000;
    int_fast64_t   a_fast64 = -9000000000000000000LL;

    // Fundamental types
    bool         a_bool = true;
    char         a_char = 'A';
    short        a_short = -32000;
    int          a_int = 1000000;
    long         a_long = 1000000000L;
    long long    a_longlong = 1000000000000LL;

    float        a_float = 3.14f;
    double       a_double = 3.1415926535;
    long double  a_longdouble = 3.141592653589793238462643383279L;

    wchar_t      a_wchar = L'Ω';
    char16_t     a_char16 = u'A';
    char32_t     a_char32 = U'😊';

    // Void pointer example (cannot declare a variable of type void)
    void*        a_voidptr = nullptr;

    // Output some values to prevent unused variable warnings
    std::cout << "int8_t: " << +a_int8 << "\n"
              << "uint8_t: " << +a_uint8 << "\n"
              << "int16_t: " << a_int16 << "\n"
              << "uint16_t: " << a_uint16 << "\n"
              << "int32_t: " << a_int32 << "\n"
              << "uint32_t: " << a_uint32 << "\n"
              << "int64_t: " << a_int64 << "\n"
              << "uint64_t: " << a_uint64 << "\n"
              << "bool: " << a_bool << "\n"
              << "char: " << a_char << "\n"
              << "wchar_t: " << a_wchar << "\n"
              << "char16_t: " << static_cast<char>(a_char16) << "\n"
              << "char32_t: " << static_cast<char>(a_char32) << "\n"
              << "float: " << a_float << "\n"
              << "double: " << a_double << "\n"
              << "long double: " << a_longdouble << "\n";

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Notes

  • +a_int8 and +a_uint8 are used when printing uint8_t/int8_t because char types print as characters by default.
  • void itself can’t be instantiated; only pointers to void (void*) are valid.
  • char16_t and char32_t values are cast when printing, because their types don’t directly stream to std::cout.
  • You can modify or extend the example for your testing or documentation.

Usage Rules

Introduction

This section will provide you with a list of yes or no questions to determined which data type you need to use to store your data in c++.

Decision Tree

Q01 - Which Data do you need to store?

Text - Char

Whole Number - Integer

Decimal Number - Float / Double

Boolean True / False - Bool (END)

Pointer / Memory Address - intptr_t / uintptr_t

Q02 - Positive or Negative Values?

Positive - Unsigned

Negative / Both - Signed

Q04 - What size values do you need to store?

Whole & Decimal Numbers:

(MIN) 128 / 0 - (MAX) 127 / 255 - 8 Bits

(MIN) -32,768 / 0 - (MAX) 32,767 / 65,535 - **16 Bits

(MIN) -2,147,483,648 / 0 - (MAX) 2,147,483,647 / 4,294,967,295 - 17 Decimal Digits Precision - 32 Bits

(MIN) -9,223,372,036,854,775,808 / 0 - (MAX) 9,223,372,036,854,775,807 / 18,446,744,073,709,551,615 - 15 decimal digits precision - 64 Bits

Whole Numbers Only:

Maximum Bits Available - 64 or More Bits

Characters:

UTF-8 - 8 Bits

UTF-16 - 16 Bits

UTF-32 - 32 Bits

Q03 - Do you have requirements?

Speed? (Fast)

Minimum Number of Bits? (8, 16, 32, 64)

Q05 - Fixed vs Dynamic Data Sizes

Performance, Portability, Use Cases (General-purpose apps, UI, platform-specific code) - Dynamic

Memory Efficiency, Safer, Better Optimization, Use Cases (Embedded systems, network protocols, file formats, security-critical code) - Fixed

Answer Table

Data Type Signed / Unsigned Fixed / Dynamic Size (Bits)
bool N/A Dynamic 8 bits
signed char Signed Dynamic 8 bits
unsigned char Unsigned Dynamic 8 bits
short Signed Dynamic 16 bits
unsigned short Unsigned Dynamic 16 bits
int Signed Dynamic 32 bits (commonly)
unsigned int Unsigned Dynamic 32 bits (commonly)
long Signed Dynamic 32 bits (Windows), 64 bits (Unix)
unsigned long Unsigned Dynamic 32 bits (Windows), 64 bits (Unix)
long long Signed Dynamic 64 bits
unsigned long long Unsigned Dynamic 64 bits
int8_t Signed Fixed 8 bits
uint8_t Unsigned Fixed 8 bits
int16_t Signed Fixed 16 bits
uint16_t Unsigned Fixed 16 bits
int32_t Signed Fixed 32 bits
uint32_t Unsigned Fixed 32 bits
int64_t Signed Fixed 64 bits
uint64_t Unsigned Fixed 64 bits
intptr_t Signed Fixed Platform-dependent: 32 bits or 64 bits
uintptr_t Unsigned Fixed Platform-dependent: 32 bits or 64 bits
intmax_t Signed Fixed Maximum width supported (≥64 bits)
uintmax_t Unsigned Fixed Maximum width supported (≥64 bits)
int_least8_t / int_fast8_t Signed Fixed ≥8 bits
uint_least8_t / uint_fast8_t Unsigned Fixed ≥8 bits
int_least16_t / int_fast16_t Signed Fixed ≥16 bits
uint_least16_t / uint_fast16_t Unsigned Fixed ≥16 bits
int_least32_t / int_fast32_t Signed Fixed ≥32 bits
uint_least32_t / uint_fast32_t Unsigned Fixed ≥32 bits
int_least64_t / int_fast64_t Signed Fixed ≥64 bits
uint_least64_t / uint_fast64_t Unsigned Fixed ≥64 bits
void N/A N/A N/A

Top comments (0)