sizeof C++: A Thorough Guide to the Size of Operator in C++

In the world of C++, the sizeof C++ operator is a fundamental tool for understanding memory usage and data layout. Whether you are writing high-performance code, optimising a game engine, or simply debugging a tricky class, knowing how the size of various types is determined can save you time and headaches. This guide unpacks the sizeof C++ operator, its rules, its quirks, and how to use it effectively across modern C++ projects.
Understanding the sizeof C++ Operator
The sizeof C++ operator is a compile-time operator that yields the size, in bytes, of a type or an expression. The result is of type size_t, an unsigned integer type defined by the language standard. Importantly, sizeof C++ does not evaluate expressions in the sense of executing their side effects; instead, it inspects the static type of the expression to determine how much memory would be allocated for objects of that type. This is why you often see sizeof used with types and with static arrays rather than with values that require computation at runtime.
Key points to remember about the sizeof C++ operator:
- It can be applied to a type or to an expression:
sizeof(int)orsizeof x. - The result is a
size_tvalue representing bytes. - When applied to an array, at the array scope, it yields the total size in bytes of the array; inside a function parameter, arrays decay to pointers and the result reflects the pointer size instead.
- It does not evaluate the expression for side effects, so use with care where evaluation side effects matter.
Basic Usage: size of Primitive Types
One of the most common uses of the sizeof C++ operator is to determine the storage requirements of primitive types. This helps you write portable code that adapts to different architectures.
Examples: sizeof int, long, float
#include <iostream>
int main() {
std::cout << "Size of int: " << sizeof(int) << std::endl;
std::cout << "Size of long: " << sizeof(long) << std::endl;
std::cout << "Size of float: " << sizeof(float) << std::endl;
return 0;
}
In practice, the exact numbers depend on the compiler and platform. For many modern 64-bit systems, you might see sizeof(int) equal to 4, sizeof(long) equal to 8 on LLP64 platforms, and sizeof(float) equal to 4. The important takeaway is that sizeof C++ yields portable constants that help you reason about memory usage without needing to guess.
Arrays, Pointers and the sizeof C++ Operator
Arrays, pointers and the sizeof C++ operator interact in interesting ways. A static array’s size is the total memory it occupies, while a pointer’s size is fixed by the architecture and independent of the array it may reference.
Static arrays
#include <iostream>
int main() {
int a[10];
std::cout << "Size of a: " << sizeof a << std::endl;
std::cout << "Number of elements: " << (sizeof a / sizeof a[0]) << std::endl;
return 0;
}
In this example, sizeof C++ returns the total number of bytes occupied by the array, which is 10 times the size of an int. The second line demonstrates the classic trick for computing the number of elements in a static array. It relies on the fact that sizeof a yields the array’s total size in bytes, while sizeof a[0] yields the size of a single element.
Dynamic arrays and pointers
When you have a pointer, such as memory allocated with new or allocated on the heap, sizeof C++ yields the size of the pointer itself, not the memory it points to. The pointed-to object’s size must be tracked separately.
#include <iostream>
int main() {
int* p = new int[20];
std::cout << "Size of pointer: " << sizeof p << std::endl;
std::cout << "Size of allocated memory (unknown to sizeof): " << 20 * sizeof(int) << std::endl;
delete[] p;
return 0;
}
Note that sizeof C++ cannot deduce the length of a dynamically allocated array; you must maintain the length yourself, or use higher-level abstractions such as std::vector which encapsulate size information safely.
Classes, Structs and the sizeof C++ Operator
When applying sizeof C++ to user-defined types, the result depends on the members, inheritance, and whether the class or struct contains virtual functions. The rule of thumb is that the size is the total memory footprint of an object of that type on the current platform.
Empty classes and inherited members
#include <iostream>
struct Empty {};
int main() {
std::cout << "Size of Empty: " << sizeof(Empty) << std::endl;
return 0;
}
Even an empty class has a non-zero size in most ABIs—usually 1 byte—to ensure distinct addresses for distinct objects. If a class inherits data from another class, the base class’s size contributes to the derived class’ size, plus any additional data members in the derived class.
Virtual functions and the vptr
Adding virtual functions to a class typically introduces a hidden pointer to a virtual table (vptr). This pointer increases the object’s size by the size of a pointer on the platform. Compilers may store the vptr in a way that respects alignment and ABI nuances, so the exact size can vary between compilers and platforms.
#include <iostream>
class Base {
public:
virtual void foo() {}
};
class Derived : public Base {
int x;
};
int main() {
std::cout << "Size of Base: " << sizeof(Base) << std::endl;
std::cout << "Size of Derived: " << sizeof(Derived) << std::endl;
return 0;
}
Here, sizeof C++ for both classes reflects not just the explicit data members but also the potential overhead of runtime dispatch via the vptr where virtual functions are present. This is a crucial consideration for memory-conscious designs and high-performance code paths.
Common Pitfalls and Misconceptions
Even experienced developers trip over the language rules surrounding the sizeof C++ operator. Here are some frequent traps to avoid.
Parameter decay and function scope
#include <iostream>
void func(int a[]) { // actually decays to int* a
std::cout << "Size of a inside func: " << sizeof a << std::endl;
}
int main() {
int a[10];
std::cout << "Size of a in main: " << sizeof a << std::endl;
func(a);
return 0;
}
The array inside func decays to a pointer, so sizeof C++ inside the function yields the size of a pointer rather than the size of the original array. This is a classic example of how scope and parameter passing affect the outcome of sizeof.
String literals versus std::string
When dealing with strings, remember that a string literal is an array of characters, and thus sizeof C++ on the literal yields its full length plus the terminating null character. Conversely, a std::string object encapsulates its own memory management; the result of sizeof String shows the size of the object wrapper, not the length of the dynamically allocated character buffer it may own.
#include <iostream>
#include <string>
int main() {
const char* lit = "hello";
std::cout << "Size of literal: " << sizeof "hello" << std::endl;
std::cout << "Size of std::string object: " << sizeof(std::string) << std::endl;
return 0;
}
The takeaway is that sizeof C++ on a C-string literal yields the literal’s size at compile time, whereas the size of an intangible string object is determined by the implementation details of the class.
Portability, Alignment and Platform Differences
Memory layouts vary across compilers and platforms. While sizeof C++ provides a reliable compile-time constant, it is not a universal memory footprint across all environments. You should consider:
- Pointer size differences: On 32-bit systems, pointers are typically 4 bytes; on 64-bit systems, they’re typically 8 bytes. This directly affects the size of pointer members and any data structures that store pointers.
- Object alignment: Some ABIs require objects to be aligned to certain boundaries. Alignment padding can alter the total size of a class or struct, especially when mixing data types with different alignment requirements.
- Empty base optimisation: Some compilers apply EBO so that empty base classes do not increase the derived object size, depending on the inheritance scenario.
- Standard library implementations: The size of standard library types (like std::vector) includes internal bookkeeping, which can differ slightly between libraries and compilers.
When porting code between platforms, always verify the results of sizeof C++ for the types you rely on, and use static assertions or static_assert to guard assumptions at compile time:
#include <cstddef>
static_assert(sizeof(void*) == 8, "Pointer size mismatch on this platform!");
Best Practices and Practical Tips
To make the most of the sizeof C++ operator in real-world projects, adopt a few practical habits that improve readability, safety, and portability.
Using sizeof in templates and constexpr
Templates thrive on compile-time information. You can make code more generic and robust by using sizeof alongside constexpr to compute constants at compile time without runtime overhead.
#include <iostream>
template<typename T> constexpr std::size_t bytes() { return sizeof(T); }
int main() {
std::cout << "Bytes in double: " << bytes<double>() << std::endl;
return 0;
}
Note how the value is computed at compile time, enabling optimisations and safer code.
Guarding against misreadings with std::array
When you require an array whose size is known at compile time, prefer std::array over built-in C-style arrays. It provides a fixed size, explicit interface, and a friendlier interaction with sizeof C++ for both elements and containers.
#include <array>
#include <iostream>
int main() {
std::array arr;
std::cout << "Size of arr: " << sizeof arr << std::endl;
std::cout << "Number of elements: " << arr.size() << std::endl;
return 0;
}
Related Topics: alignof and Type Traits
For deeper control over memory layout and type properties, explore the pair of features that complement the sizeof C++ operator:
- alignof: determines the alignment requirement of a type in bytes. This is useful for custom allocators and low-level memory management.
- Type traits (from
<type_traits>): compile-time utilities to query or modify type properties. Combined withsizeof, you can write highly generic and portable code.
#include <iostream>
#include <type_traits>
int main() {
std::cout << "Alignment of int: " << alignof(int) << std::endl;
std::cout << "Is trivially copy assignable int: " << std::is_trivially_copy_assignable<int>::value << std::endl;
return 0;
}
Common Questions About the sizeof C++ Operator
Below are quick explanations for a few frequent inquiries that programmers have when working with sizeof C++.
Does sizeof evaluate expressions with side effects?
No. If you use sizeof on an expression, the expression is not evaluated; only its type is considered. This is why code like sizeof (i++) does not increment i.
Can I use sizeof for dynamic memory sizing?
Not directly. For dynamic memory, you usually need to track the number of elements separately or use container classes such as std::vector that manage their own size information. Use sizeof C++ for static objects and for types that your compiler can statically reason about.
Is the result of sizeof portable across compilers?
While the language specification guarantees that sizeof yields a compile-time constant of type size_t, the exact numeric value can vary with architecture and ABI. Always test on each target platform you intend to support, and avoid relying on a particular byte size unless it is essential and well-documented in your project guidelines.
Conclusion: Mastery of the sizeof C++ Operator
With a clear understanding of the sizeof C++ operator, you gain a powerful lens into memory usage, object layout, and the behaviour of complex data structures. The operator works seamlessly with primitive types, arrays, pointers, user-defined types, and templates, enabling you to write portable, efficient, and robust C++ code. By recognising common pitfalls—such as array decay, the role of virtual tables, and platform differences—you can leverage sizeof C++ to inform design decisions, optimise performance, and reduce surprises in production environments.