How Many Bytes In A Pointer In C? | It Depends On Your Build

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 size
  • sizeof(int*), sizeof(char*), sizeof(double*) to confirm object-pointer sizes match on your target
  • sizeof(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 sizeof results with %d or %lu instead of %zu for size_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*) and sizeof(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_t for sizes and element counts that come from sizeof.
  • Use sizeof(ptr) only when you truly want pointer size. Use sizeof(*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 %zu for size_t and %p for 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