C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
8: Fundamental Types and Their Sizes
I’ve seen plenty of developers start their C++ journey by treating int as the universal hammer. If it's a number, it's an int. For a while, that works. Your code compiles, your logic holds, and you don't even think about it. But here is the problem: in C++, an int is a chameleon. Its size isn't actually fixed by the language standard; it's defined by the implementation (the compiler and the architecture). On most modern systems, it's 32 bits, but that's not a guarantee you can bet your career on.
The danger of the "Standard" Integer
Imagine you're building a system to track player statistics for a game. You decide to use int for everything: health, experience points, and the total number of items in a massive world database. On your development machine, everything is great. But then you deploy to an embedded system or a different architecture where int might behave differently, or you realize that your xp variable just wrapped around into a negative number because the player hit the 2.1 billion mark (the limit of a signed 32-bit integer).
// The naive approach
struct PlayerStats {
int health; // Why use 32 bits for 0-100?
int experience; // What happens at 2,147,483,648?
int itemCount; // Can a player have -5 items?
};
The trade-off here is convenience versus predictability. Using int is fast to type, but it hides the actual constraints of your data. You're essentially telling the compiler, "Just give me whatever size you think is okay," which is a dangerous way to manage memory and logic when you're working in a language that gives you direct access to the hardware.
Precision with Fixed-Width Types
If you want your code to be portable and robust, you should reach for the <cstdint> header. This provides types like int32_t, uint64_t, and int8_t. The "t" stands for type, and the number tells you exactly how many bits you're getting. I always recommend these when the size of the data actually matters—like when you're writing data to a binary file or sending a packet over a network. If you tell a server to expect a 32-bit integer and you send it a 64-bit one, you've just corrupted your data stream.
#include <cstdint>
struct PlayerStats {
uint8_t health; // 0 to 255, takes only 1 byte. Perfect.
uint64_t experience; // Massive range, won't overflow easily.
uint32_t itemCount; // Unsigned because items can't be negative.
};
Now, you might think, "Why not just use uint64_t for everything to be safe?" Because memory isn't free. If you have a million PlayerStats objects in a vector, the difference between a 1-byte uint8_t and an 8-byte uint64_t is 7 megabytes of wasted RAM. In a high-performance C++ application, that's the difference between fitting your data in the CPU cache or hitting the much slower main memory.
When to use size_t
There is one more type that often trips people up: std::size_t. You'll see this everywhere in the C++ standard library. It's not a fixed size like int32_t; instead, it's an unsigned type guaranteed to be big enough to hold the maximum size of any object your system can possibly allocate.
I see a lot of people use int i = 0 in their loops. While it works, it's technically a mistake. If you're indexing into a very large array, an int might overflow before you reach the end of the container, or the compiler will nag you with "signed/unsigned mismatch" warnings. Using size_t for indices and counts is the "correct" way because it scales with the architecture—it's 32-bit on 32-bit systems and 64-bit on 64-bit systems.
📋 Practical Task
Refactoring the Inventory Memory Layout
You have been handed a legacy piece of code for a warehouse management system. The current implementation is wasteful and prone to overflow. Your task is to refactor the WarehouseItem struct to use the most appropriate fundamental types from <cstdint> and std::size_t based on the following requirements:
id: A unique identifier that will never exceed 4 billion.quantity: A count of items that can range from 0 to 65,535.weightGrams: A precise weight that could potentially be very large (up to 18 quintillion grams).categoryCode: A small code from 0 to 255.
Additionally, rewrite the provided for loop that iterates through the items array to use the correct type for the index variable to avoid signed/unsigned warnings.
#include <iostream>
#include <vector>
// TODO: Refactor this struct
struct WarehouseItem {
int id;
int quantity;
int weightGrams;
int categoryCode;
};
int main() {
std::vector<WarehouseItem> inventory(1000);
// TODO: Fix the loop index type
for (int i = 0; i < inventory.size(); ++i) {
// Processing items...
}
return 0;
}
There are no comments for now.