In C, a pointer is commonly 4 bytes on 32-bit builds and 8 bytes on 64-bit builds, yet the language lets each platform choose.
You’ll see people answer this with “4 or 8.” That’s a solid guess on desktops and servers, yet it’s not the rule C promises. The real answer is: a pointer’s size comes from your target platform and ABI (the rules your OS + compiler follow).
If you’re debugging a crash, sizing a buffer, writing a serializer, or dealing with FFI, guessing can burn you. This article shows what controls pointer size, how to verify it in seconds, and where pointer-size assumptions quietly break code.
What A Pointer Stores And Why Size Varies
A pointer value holds an address (or something that behaves like an address) used to locate an object or a function in memory. To do that job, it needs enough bits to represent the address range your build can reach.
That range is not universal. A 32-bit build often uses 32-bit addresses, so an object pointer lands at 4 bytes. A 64-bit build often uses 64-bit addresses, so an object pointer lands at 8 bytes. Some embedded targets use smaller pointers. Some segmented or capability-style systems use bigger pointers.
C’s model stays flexible: the language defines what you can do with pointers, not the exact bit layout used by every CPU and OS combo. That flexibility is why C runs on tiny microcontrollers and giant servers without changing the core language rules.
How Many Bytes In A Pointer In C? On 32-Bit Vs 64-Bit Builds
On mainstream systems today, you’ll most often see these results:
- 32-bit build: object pointers are often 4 bytes
- 64-bit build: object pointers are often 8 bytes
Still, “64-bit” does not mean every pointer is 8 bytes in every scenario. Some ABIs use 8-byte pointers while keeping some integers at 4 bytes. Some systems use a 64-bit CPU while running a 32-bit userland, giving 4-byte pointers inside that process.
Also, not all pointer categories need to match each other. Function pointers can differ from object pointers on some targets. Pointers to different address spaces can differ on certain embedded toolchains. Most desktop builds keep them the same, yet portable C code should avoid relying on that.
How To Measure Pointer Size The Right Way
There’s one reliable answer for your exact build: ask the compiler. The sizeof operator returns the size in bytes of a type or object, and it works perfectly for pointers. The result type is size_t.
These are the checks you’ll use most:
sizeof(void*)for a generic object-pointer sizesizeof(int*),sizeof(char*),sizeof(double*)to confirm object-pointer sizes match on your targetsizeof(void (*)(void))to inspect a function-pointer size
If you want a quick sanity printout, log the values at startup in a debug build. Keep the print format correct for size_t (use %zu in C99+).
You can read the operator’s rules on cppreference’s sizeof documentation, and Microsoft’s C reference also calls out common cases like arrays vs pointers with MSVC’s sizeof operator page.
Why “Byte” In C Means Something Specific
When C says “byte,” it means sizeof(char) bytes, and that value is always 1. People often equate a byte with 8 bits, and that matches most modern machines. C still allows other sizes as long as a byte has at least 8 bits.
So when you see “pointer is 8 bytes,” that’s eight C bytes. On normal desktop systems, that maps to 64 bits. On unusual targets, the bit count behind “a byte” can differ.
Common Pointer Sizes By Platform Style
Instead of memorizing myths, tie pointer size to the platform’s data model. A “data model” is the convention that sets the widths of key types like int, long, and pointers. You’ll hear names like ILP32 and LP64 in toolchain docs.
Here’s a practical cheat sheet you can use when you’re reasoning about someone else’s build, reviewing a bug report, or scanning a CI matrix.
| Typical Build / Data Model | Common Pointer Size | Notes You’ll See In Practice |
|---|---|---|
| ILP32 (many 32-bit OSes) | 4 bytes | int, long, and pointers often align at 32 bits |
| LP64 (many 64-bit Unix-like OSes) | 8 bytes | long and pointers often 64-bit; int often 32-bit |
| LLP64 (common on 64-bit Windows) | 8 bytes | long long is 64-bit; long often stays 32-bit |
| 32-bit app on 64-bit OS | 4 bytes | Process bitness controls pointer size more than CPU marketing labels |
| Many 16-bit / small embedded targets | 2 bytes (often) | Address space and memory map drive this; toolchain docs matter |
| Mixed address-space embedded C | Varies | Different pointer types can represent different memory regions |
| Capability-based or tagged-pointer systems | 8–16 bytes (varies) | Pointers may carry bounds or metadata, changing representation size |
| Kernel vs user code on same CPU | Varies | Some environments use different ABIs or modes with different widths |
The Mistakes That Make Pointer Size Bugs Feel “Random”
Pointer-size bugs show up as crashes, corrupted strings, weird struct values, or “works on my machine” mysteries. The code often looks fine at a glance. These are the usual culprits.
Storing Pointers In The Wrong Integer Type
Don’t store a pointer in int or long and hope it fits. That might limp along on some 32-bit builds, then break on a 64-bit build where pointers are 8 bytes and int is still 4.
If you truly need to convert a pointer to an integer, use the standard integer types designed for that job (uintptr_t or intptr_t) when available, and treat that path as low-level code with real constraints.
Using The Wrong Printf Format
Two common print mistakes:
- Printing
sizeofresults with%dor%luinstead of%zuforsize_t - Printing pointer values with integer formats instead of
%p
On some platforms those mistakes only produce ugly logs. On others they cause undefined behavior and can crash, since variadic functions rely on exact argument sizes.
Confusing Array Size With Pointer Size
This one bites even seasoned C devs in a hurry. In many expressions, an array value “decays” to a pointer to its first element. That means sizeof(array) and sizeof(pointer) can produce totally different results depending on where the code runs.
At the point where the array exists as an array object, sizeof(array) yields the total storage of the whole array. After passing it to a function parameter, it behaves like a pointer, and sizeof(param) yields the pointer size.
Assuming All Pointer Types Match
Many common platforms make sizeof(int*), sizeof(char*), and sizeof(void*) equal. That’s typical for flat address spaces. It’s not a rule you can lean on for every target.
Function pointers can be a different size than object pointers on some systems. If you’re writing generic containers, callbacks, plugin loaders, or FFI glue, keep that distinction in mind.
How Pointer Size Interacts With Struct Layout And Memory Use
Pointers don’t just affect pointer math. They also change the size and alignment of structs, which then affects cache usage, file formats, network packets, and the cost of big arrays of records.
A struct that holds several pointers can double in size when moving from a 32-bit to a 64-bit build. That can raise memory pressure, change alignment padding, and slow down tight loops that walk arrays of those structs.
If you’re tuning performance, measure. A small redesign like replacing a pointer-heavy node structure with indices into a pool can cut memory use and improve locality. That’s not always the right trade, yet it’s worth considering when a data structure explodes in size under a 64-bit build.
When You Need Portability, Treat Pointer Size As Unknown
Portable C code does not assume pointer size. It asks for it with sizeof, uses standard types for sizes (size_t), and avoids baking pointer widths into serialized formats.
If you’re designing a file format or wire protocol, don’t write raw pointers into it. A pointer is only meaningful inside one process, and its representation can change across builds. Store offsets, indexes, or explicit integer IDs that you control.
If you’re working with hashing or checksums and you want to mix in pointer values for debugging or caching, keep the behavior scoped to a single build configuration and avoid making it part of a persistent contract.
| Task | Safe Pattern | Common Trap |
|---|---|---|
| Get pointer size | sizeof(void*) |
Hard-coding 4 or 8 |
| Track object sizes | size_t for counts and sizes |
Using int and overflowing on large buffers |
| Print sizes | printf("%zu", (size_t)sizeof(T)) |
%d or %lu with mismatched types |
| Print a pointer value | printf("%p", (void*)ptr) |
Printing as an integer type |
| Serialize references | Offsets, indexes, stable IDs | Writing pointers into files or packets |
| Pointer-to-integer round trip | uintptr_t when available |
Casting through int or assuming long fits |
| Generic storage | void* for object pointers |
Assuming function pointers fit in void* |
| Compute element count | sizeof(arr)/sizeof(arr[0]) in the same scope |
Doing that on a function parameter |
Fast Ways To Confirm Build Bitness Without Guessing
If you just need to confirm what your binary is doing, these checks are simple and reliable:
- Log
sizeof(void*)andsizeof(size_t)at startup - Check compiler predefined macros for the target in your build system (treat them as build-time hints, not runtime truth)
- On hosted systems, inspect the built artifact with platform tools that report whether it’s a 32-bit or 64-bit executable
Runtime logging is often the least error-prone, since it reports what the compiler actually produced.
Edge Cases Worth Knowing If You Work Outside Desktops
Most people reading this are on x86_64, ARM64, or similar targets where object pointers are 8 bytes in a 64-bit build. If you work on embedded systems, DSPs, older consoles, or safety-focused toolchains, the pointer story can be more varied.
Near And Far Pointers On Segmented Memory Models
Some older or specialized systems split addresses into multiple parts. A pointer might include a segment selector plus an offset. That kind of design can make pointer sizes larger than you’d guess from “bitness” marketing.
Multiple Address Spaces
Some microcontrollers separate program memory and data memory. A pointer that targets flash can be a different type than a pointer that targets RAM, with different representation and size.
Pointers With Metadata
Some designs attach metadata to pointers, like bounds or tags. That can change pointer representation and size. If you’re building libraries meant for many targets, this is another reason to treat pointer size as something you measure, not something you assume.
A Practical Checklist For Real Code
If your goal is “this works on 32-bit and 64-bit builds,” use this checklist in code reviews:
- Use
size_tfor sizes and element counts that come fromsizeof. - Use
sizeof(ptr)only when you truly want pointer size. Usesizeof(*ptr)when you want the pointed-to object size. - Keep array-size math in the same scope as the array declaration.
- Don’t convert pointers to smaller integer types.
- Use
%zuforsize_tand%pfor pointers in logs. - Don’t put pointers into files, databases, or network messages as a representation of identity.
Once you internalize those, pointer size stops being a mystery and turns into one more predictable property of your build.
References & Sources
- cppreference.“sizeof operator (C).”Defines how sizeof reports the size in bytes of a type or object, including pointer operands.
- Microsoft Learn.“sizeof Operator (C).”Explains sizeof behavior in C on MSVC, including common pointer-vs-array cases.
