student@ubuntu:~$
advanced-c Lesson 14 9 min read

Void Pointers & Generics

How C achieves type-agnostic code with void* — and why you must cast before use

Reading: Hanly & Koffman: §13.1 (pp. 706–710, general pointers only)

Quick check before you start: In Java, Object is the root of every class — you can store anything as an Object reference. C’s void* serves the same role and shows up wherever a function needs to accept any type.

Practice this topic: Void Pointers skill drill

About Lab 5: Lab 5 uses typed function pointers (the Power typedef) and never writes a void* cast. The void* pattern in this lesson is what production C uses when the signature is not known at compile time: dlsym for loading shared libraries, plug-in dispatch tables, and the comparator parameter of qsort you saw in Lesson 9a. Same dispatch idea; one extra cast step when the type is opaque.

After this lesson, you will be able to:

  • Explain what void* means and why it exists
  • Cast a void* to a typed pointer before dereferencing
  • Describe how qsort uses void* to sort any type
  • Compare void* to Java’s Object type

What Is void*?

A void* is a pointer with no type information. It holds a memory address, but the compiler does not know what lives at that address.

int x = 42;
void *ptr = &x;  // legal — any pointer can become void*

You can assign any pointer to a void* without a cast. But you cannot dereference it directly:

printf("%d\n", *ptr);  // COMPILER ERROR — what type is *ptr?

The compiler does not know whether to read 1 byte, 4 bytes, or 8 bytes. You must cast first.

Casting Before Dereference

To use the data behind a void*, cast it to the correct type:

int x = 42;
void *ptr = &x;

int *iptr = (int*)ptr;
printf("%d\n", *iptr);  // prints 42

If you cast to the wrong type, you get garbage or a crash. There is no runtime check.

How qsort Uses void*

Look at qsort’s signature:

void qsort(void *base, size_t count, size_t size,
            int (*cmp)(const void*, const void*));

Every parameter is type-agnostic:

  • base is void* — it accepts arrays of any type
  • size tells qsort how many bytes per element
  • cmp receives const void* pointers — the comparator knows the real type

This is how one function sorts arrays of int, double, char*, or Student — the caller provides the type knowledge through the comparator.

int compare_doubles(const void *a, const void *b) {
    double x = *(const double*)a;
    double y = *(const double*)b;
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
}

double arr[] = {3.1, 1.4, 2.7};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(double), compare_doubles);

Same qsort, different comparator, different type. That is generic programming in C.

void* Is Java’s Object

Java C
Object void*
Downcast with (String)obj Cast with (char*)ptr
ClassCastException at runtime Undefined behavior (no check)
Generics add compile-time safety No compile-time check; correctness is on the caller

Java generics are syntactic sugar over Object with compile-time checks. C gives you the raw mechanism without the safety net.

Rules for void*

  1. Any pointer converts to void* implicitly; no cast is needed on the way in.
  2. To dereference a void*, you must cast it first. The compiler does not know what type lives at the address.
  3. Pointer arithmetic on void* is illegal. The compiler does not know the element size to advance by.
  4. Casting to the wrong type is undefined behavior, with no runtime check.

Check Your Understanding
Why must you cast a void* before dereferencing it?
ABecause void* pointers are read-only
BBecause void* is always NULL
CBecause the compiler does not know the size or type of the data, so it cannot generate the correct memory access
DBecause void* requires special hardware instructions
Answer: C. A void* carries no type information. The compiler needs to know whether to read 1 byte (char), 4 bytes (int), 8 bytes (double), or something else. Casting tells the compiler the actual type so it can generate the correct dereference instruction.

What Comes Next

You understand void pointers and function pointers separately. Next, you will combine them to build generic data structures — containers that store any type, just like Java’s ArrayList<T>.

Next: Generic Data Structures