Explaining how sizeof actually works with arrays.

Sizeof on an Array Parameter Gives You the Wrong Answer

I spent three weeks of my life in a high-frequency trading shop chasing a memory corruption bug that shouldn’t have existed. I was staring at a hex dump, convinced my logic was sound, only to realize I had fundamentally misunderstood how sizeof actually works when applied to a pointer-to-member within a complex inheritance hierarchy. Most tutorials treat `sizeof` like a magical, static property of a type—a simple lookup table for bytes—but that’s a dangerous simplification. If you treat it as a runtime measurement rather than a compile-time instruction to the compiler, you aren’t just writing code; you’re gambling with your memory layout.

I’m not here to give you the textbook definition you can find in any introductory manual. My goal is to strip away the abstractions and show you the actual mechanics of the operator, from padding and alignment to the way the compiler evaluates expressions during the translation phase. We are going to look at the rules that bite, focusing on the edge cases where your assumptions about object size will inevitably fail you. By the end of this, you’ll understand the language as it actually behaves, not as the documentation wishes it did.

Table of Contents

The Compile Time Operator vs Runtime Deception

The Compile Time Operator vs Runtime Deception.

The biggest mistake I see is treating `sizeof` like a function. It isn’t. It’s an operator, and for almost every type you’ll ever touch, it is evaluated entirely by the compiler. When you write `sizeof(int)`, the compiler isn’t generating code to go count bytes in memory; it’s simply substituting a constant value into your source code before the binary is even minted. This is the fundamental compile time operator vs runtime distinction that trips up developers migrating from languages like Python or JavaScript, where “size” is a dynamic property of an object.

However, that’s where the deception starts. Because `sizeof` is a constant expression, it can lead to a false sense of security regarding how much memory you are actually touching. Take the classic sizeof on pointer vs array blunder: if you pass a stack-allocated array to a function, it decays into a pointer. If you call `sizeof` on that parameter, you aren’t getting the array’s capacity; you’re getting the size of the address itself. You’ll think you’ve allocated enough space, but you’ve actually just measured the width of a pointer.

The Sizeof Operator Return Type You Cant Ignore

The Sizeof Operator Return Type You Cant Ignore

Most developers treat `sizeof` like a magic function that returns an `int`. It doesn’t. If you’re writing generic code or templated logic and you assume a standard signed integer is coming back, you’re asking for an overflow or a type mismatch that the compiler might only catch if you’re lucky. The sizeof operator return type is actually `std::size_t`, which is an unsigned integer type.

This distinction isn’t just academic pedantry; it’s the difference between a clean build and a silent logic error. When you start doing math with `sizeof`—say, calculating offsets for a custom memory allocator—mixing `size_t` with signed `int` types can lead to unsigned wrap-around bugs. I’ve seen junior devs try to subtract a larger `sizeof` value from a smaller one, expecting a negative result, only to end up with a massive positive number that triggers a catastrophic out-of-bounds memory access. If you aren’t respecting the unsigned nature of the result, you aren’t actually in control of your memory.

Five ways to stop fighting the compiler

  • Stop treating `sizeof` like a function. It isn’t. It’s a compile-time operator. If you pass it something that requires a runtime calculation, you aren’t “calculating size”—you’re likely hitting an undefined behavior or a compiler error that you’ll spend three hours trying to decipher.
  • Respect the `size_t` type. I see people casting the result to `int` all the time. It’s lazy and dangerous. On a 64-bit system, an `int` can’t hold the size of a large memory allocation. Use the unsigned type the language gave you; don’t try to “simplify” it.
  • Remember that `sizeof` on a pointer is not `sizeof` on the thing it points to. This sounds obvious, but in template metaprogramming, it’s where the bodies are buried. If you lose the distinction between the address and the object, your memory math is dead on arrival.
  • Don’t forget about padding. If you’re using `sizeof` to manually calculate offsets for a custom allocator or a binary protocol, you’re playing with fire. The compiler inserts invisible bytes to satisfy alignment requirements; `sizeof` includes them, whether you see them in your struct definition or not.
  • Watch out for Flexible Array Members. If you’re working with C-style structs that use a trailing array for variable-length data, `sizeof` will only give you the size of the header. It won’t see the payload. If you rely on it for the total allocation size, you’re going to have a heap overflow.

The Bottom Line

Stop treating `sizeof` like a runtime function; it is a compile-time operator that evaluates the type’s footprint, not the object’s current state.

Watch your types. Because `sizeof` returns `size_t`, mixing it with signed integers is a fast track to overflow bugs that only show up when your data structures scale.

Respect the object model. If you aren’t accounting for padding and alignment rules, the value `sizeof` gives you will be the truth, even if it’s not the truth you expected.

Stop Guessing, Start Verifying

If you walk away with nothing else, remember this: `sizeof` is not a function, it is a compile-time directive that operates on types and complete objects. It doesn’t “calculate” anything while your code is running; it asks the compiler for a value that is already baked into the binary. When you treat it like a runtime utility—especially when dealing with pointer decay or trying to measure the size of a dynamic heap allocation—you are essentially playing Russian roulette with your memory layout. You must respect the distinction between the size of the pointer and the size of the data it points to, or you’ll find yourself chasing ghosts in a debugger for three days straight.

C++ is a language of precision, and that precision requires you to look past the syntax and into the actual mechanics of the machine. Don’t just memorize the rules; understand why the compiler enforces them. Once you stop treating operators like magic spells and start viewing them as instructions for the toolchain, you move from being someone who just writes code to someone who actually engineers systems. The bugs that bite the hardest are almost always the ones born from a lack of respect for these fundamental truths. Go build something that actually works.

About Ruaridh Kensington-Oyelaran

C++ rewards people who know what the compiler is allowed to do. I write about the rules that bite, the ones nobody mentions until you have already shipped the bug.

More From Author

Understanding memory ordering basics in multithreading.

Your Writes Do Not Reach Other Threads in the Order You Wrote Them