Skip to main content

void*, Element Size & memcpy

intermediate16 min readLesson 117 of 148

A generic array is (pointer, count, element_size). Everything else follows.

The universal triple

Every generic array function in C has the same skeleton:

void *my_lsearch(const void *key, const void *base, size_t n,
                 size_t size, int (*cmp)(const void *, const void *));

base points at element 0, n counts elements, size is one element's byte size, cmp knows the real type. Inside, element i lives at:

const char *p = (const char *)base + i * size;

char* arithmetic is byte arithmetic โ€” that is the one guaranteed addressable unit. Two disciplines keep this safe:

1. Never cast to the wrong type โ€” cast to char * and move bytes. Copying an element is memcpy(dst, src, size), never a struct or pointer dereference. void * converts to/from any object pointer implicitly in C โ€” no cast needed on either side โ€” but you still cannot dereference it.

2. Alignment is inherited, not manufactured. If base really points at an array of T, every i * size offset is a valid T address. The caller's types stay aligned because you never invent storage of your own.

The swap primitive

Generic algorithms bottom out in byte-wise element swaps:

static void swap_bytes(char *a, char *b, size_t size) {
    while (size--) {
        char t = *a;
        *a++ = *b;
        *b++ = t;
    }
}

One byte at a time is slow but universally correct; memcpy through a small stack buffer is the faster classical form. Overlapping elements would be UB โ€” the caller's contract (distinct element slots) is what makes the primitive legal.

Now practice

void* Engine GymGeneric search, reverse, and dedup over raw bytes.1 challenge ยท ยท ~26 min