Why Void Pointer Arithmetic Is Disallowed in C
In the C programming language, the strict ISO standard forbids direct
pointer arithmetic on void pointers because
void is defined as an incomplete type with an indeterminate
size. Because pointer arithmetic fundamentally relies on knowing the
byte size of the pointed-to object to compute memory address offsets, an
expression like ptr + 1 cannot be resolved without a
concrete type. This article explains the technical mechanics behind
pointer arithmetic in C, the concept of incomplete types, and the
standard-compliant methods used to perform byte-level memory
calculations.
The Mechanics of Pointer Arithmetic
In C, pointer arithmetic is automatically scaled by the size of the type being pointed to. When an integer is added to a pointer, the compiler does not simply add that raw integer value to the memory address. Instead, it multiplies the integer by the size of the underlying data type.
For example, given a pointer T *ptr:
ptr + n = (uintptr_t)ptr + (n * sizeof(T))
If ptr is an int* and
sizeof(int) is 4 bytes, ptr + 1 increases the
address by 4 bytes. If ptr is a double* on an
architecture where sizeof(double) is 8 bytes,
ptr + 1 increases the address by 8 bytes. This scaling
ensures that incrementing a pointer advances it to the next logical
element in an array rather than an arbitrary byte.
The Role of
void as an Incomplete Type
The void type represents the absence of value or an
unknown type. Under the ISO C standard (ISO/IEC 9899), void
is categorized as an incomplete type that cannot be completed.
Because void represents no defined object or storage
layout:
- An object of type
voidcannot exist. - The expression
sizeof(void)is illegal and invalid in standard C.
Without an established size, the formula n * sizeof(T)
cannot evaluate. The compiler cannot determine whether
ptr + 1 should advance the memory address by 1 byte, 4
bytes, 8 bytes, or any other interval. Consequently, standard C treats
any attempt to perform additive or subtractive operations on a
void* as a compile-time error.
Intent of void* in
the Standard
The void* generic pointer was introduced in ANSI C to
represent a raw memory address without imposing type constraints. It
serves as a generic handle for passing data to functions like
malloc, memcpy, and qsort.
Allowing pointer arithmetic on void* would compromise
the type-safety assumptions of the language. A void*
explicitly signals that the compiler does not know what is stored at
that location. Requiring the programmer to explicitly cast the pointer
before manipulating it ensures that the programmer's intent regarding
element size is clearly stated in the code.
The GNU C Extension
Certain compilers, such as GCC and Clang, support a non-standard
extension that assigns a size of 1 to void. Under this
extension, sizeof(void) evaluates to 1,
allowing arithmetic on void* as if it were a pointer to
bytes.
However, relying on this behavior makes code non-portable. Compiling
with strict standard compliance flags, such as
-std=c11 -pedantic-errors in GCC, will reject pointer
arithmetic on void* and generate an error.
Standard-Compliant Workarounds
To perform byte-wise pointer arithmetic in standard-compliant C, the
void* must first be cast to a pointer of a type with a
guaranteed size of 1 byte:
char*: By definition in the C standard,sizeof(char)is always1.uint8_t*: Defined in<stdint.h>, this explicitly documents that the memory is being manipulated as unsigned 8-bit bytes.
void *buffer = get_raw_buffer();
/* Non-compliant: compilation error under strict ISO C */
/* void *next = buffer + 16; */
/* Compliant: explicit cast to a 1-byte type */
char *next_char = (char *)buffer + 16;
uint8_t *next_byte = (uint8_t *)buffer + 16;By casting to a concrete 1-byte type, the pointer arithmetic formula resolves unambiguously, maintaining portability and adhering strictly to the ISO C standard.