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:

  1. An object of type void cannot exist.
  2. 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:

  1. char*: By definition in the C standard, sizeof(char) is always 1.
  2. 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.