Generic code
C has no templates. A function that works on any type takes a void * and the size of the object, and a macro or a _Generic selection supplies the part that depends on the type. The compiler cannot check the type inside the function, so the size and the comparator are the caller's promise.
void * and a size
A void * converts to and from any object pointer without a cast, and memcpy moves a number of bytes. Together they swap values of any type whose size is passed in. The temporary buffer is a byte array, because the function does not know the element type.
#include <stdio.h>
#include <string.h>
static void swap(void *a, void *b, size_t size)
{
unsigned char temporary[64];
memcpy(temporary, a, size);
memcpy(a, b, size);
memcpy(b, temporary, size);
}
int main(void)
{
int x = 1;
int y = 2;
double p = 1.5;
double q = 2.5;
swap(&x, &y, sizeof x);
swap(&p, &q, sizeof p);
printf("ints: %d %d\n", x, y);
printf("doubles: %.1f %.1f\n", p, q);
return 0;
}
gcc -Wall -Wextra swap.c -o swap
./swap
ints: 2 1
doubles: 2.5 1.5
The fixed 64-byte temporary is enough for the types here; a real generic function would allocate it or take a maximum size. Passing the wrong size is undefined behaviour, and the compiler cannot see the mistake because the call has no type information.
_Generic selects a type
_Generic chooses an expression by the type of its controlling argument. It is evaluated at compile time, so the selected string is just a constant, and the default association catches every type that has no explicit one. The macro below prints an int, a double or a string with the right conversion.
#include <stdio.h>
#define SHOW(x) printf(_Generic((x), \
int: "%d\n", \
double: "%.2f\n", \
char *: "%s\n"), (x))
int main(void)
{
SHOW(42);
SHOW(3.5);
SHOW("text");
return 0;
}
gcc -Wall -Wextra show.c -o show
./show
42
3.50
text
The controlling expression is not evaluated, only its type is used, so SHOW(i++) would not increment twice. The association list is fixed at compile time; a type that is not listed and no default is an error, which the last section shows.
A generic search
The search below walks an array of unknown element type: it advances by size bytes per element and calls a comparator for each one. This is the shape of bsearch from the standard library, which lesson 54 uses directly. The comparator is the same callback pattern as qsort in lesson 29.
#include <stdio.h>
static const void *find(const void *base, size_t count, size_t size,
const void *key, int (*compare)(const void *, const void *))
{
const unsigned char *bytes = base;
for (size_t i = 0; i < count; i++) {
if (compare(bytes + i * size, key) == 0) {
return bytes + i * size;
}
}
return NULL;
}
static int compare_int(const void *left, const void *right)
{
int a = *(const int *) left;
int b = *(const int *) right;
return (a > b) - (a < b);
}
int main(void)
{
int values[5] = {10, 20, 30, 40, 50};
int key = 30;
const int *found = find(values, 5, sizeof values[0], &key, compare_int);
if (found == NULL) {
printf("not found\n");
} else {
printf("found at index %td\n", found - values);
}
return 0;
}
gcc -Wall -Wextra find.c -o find
./find
found at index 2
The byte pointer does the arithmetic, and the result converts back to const int * because the function promises to return a pointer into the same array. The subtraction is done in int elements, which is why the index is 2 and not 8.
Type-safe macros
A macro can be generic without a void * when the array type is visible at the call. ARRAY_LEN divides the size of the array by the size of one element, and it fails to compile if the argument is a pointer, which is the safety net the sizeof version lacks.
#include <stdio.h>
#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0])
int main(void)
{
int values[7] = {0};
for (size_t i = 0; i < ARRAY_LEN(values); i++) {
printf("%zu ", i);
}
printf("\nlength: %zu\n", ARRAY_LEN(values));
return 0;
}
gcc -Wall -Wextra len.c -o len
./len
0 1 2 3 4 5 6
length: 7
Called with a pointer, the macro would divide the size of a pointer by the size of an int and report a plausible but wrong number. The protection is that a pointer has no [0] element to take the size of, so the expression does not compile.
Wrong size, no association
sizeof on a pointer gives the size of the pointer, not of the array it points at. A copy function that uses sizeof source therefore copies eight bytes instead of the string; GCC recognises the pattern and warns:
#include <stdio.h>
#include <string.h>
static void copy(char *target, const char *source)
{
memcpy(target, source, sizeof source);
}
int main(void)
{
char target[16] = {0};
copy(target, "hello");
printf("%s\n", target);
return 0;
}
gcc -Wall -Wextra badsize.c -o badsize
badsize.c: In function 'copy':
badsize.c:6:35: warning: argument to 'sizeof' in 'memcpy' call is the same expression as the source; did you mean to provide an explicit length? [-Wsizeof-pointer-memaccess]
6 | memcpy(target, source, sizeof source);
| ^~~~~~
The program is not run here: it copies the pointer's eight bytes, which happens to include the first eight characters of the literal but is not the copy that was intended. The fix is to pass the capacity like the other examples in this lesson.
A _Generic without a matching association and without a default is a compile error. The message names the controlling type and the macro expansion line:
#include <stdio.h>
#define KIND(x) _Generic((x), int: 1, double: 2)
int main(void)
{
printf("%d\n", KIND("text"));
return 0;
}
gcc -Wall -Wextra nomatch.c -o nomatch
nomatch.c: In function 'main':
nomatch.c:3:26: error: '_Generic' selector of type 'char *' is not compatible with any association
3 | #define KIND(x) _Generic((x), int: 1, double: 2)
| ^
nomatch.c:7:20: note: in expansion of macro 'KIND'
7 | printf("%d\n", KIND("text"));
| ^~~~
A string literal has type char * here, not the array type it would have in sizeof, which is why the association for char * is the one to add. Adding a default is the alternative when every type should be accepted.
Exercises
- Extend SHOW with associations for long and float and test both.
- Use the generic search on an array of doubles with a comparator that compares with an epsilon.
- Write a generic count_if that takes a byte array, an element size and a callback returning a boolean for one element.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What must the caller supply to a
void *generic function?Answer
The element size and a matching comparator
The function sees only bytes, so the size and the comparator are the promises that make the operation correct.
-
When is a
_Genericassociation selected?Answer
At compile time, from the controlling type
_Genericchooses by type during compilation and does not evaluate the controlling expression. -
How does a generic search move through an array of unknown type?
Answer
It advances by the element size in bytes
A byte pointer plus
i * sizereaches each element, and the comparator interprets the bytes. -
Why is
ARRAY_LENsafer than a function taking a pointer?Answer
It fails to compile when given a pointer
The macro uses
sizeofon the array itself, and a pointer has no[0]element to size, so misuse does not compile. -
What does this program print?
#include <stdio.h> #include <string.h> static void swap(void *a, void *b, size_t n){ unsigned char t[16]; memcpy(t,a,n); memcpy(a,b,n); memcpy(b,t,n); } int main(void){ int x=1,y=2; double p=1.5,q=2.5; swap(&x,&y,sizeof x); swap(&p,&q,sizeof p); printf("ints: %d %d\ndoubles: %.1f %.1f\n",x,y,p,q); return 0; }Answer
ints: 2 1 / doubles: 2.5 1.5
Both pairs are swapped by the same byte-copy function, so each pair reverses its order.
-
What does the
SHOWmacro print for 42, 3.5 and"text"?Answer
42, 3.50 and text
The
intassociation uses%d, thedoubleone uses%.2f, and the string one uses%s. -
What does the generic search print for the key 30?
Answer
found at index 2
The element at index 2 holds 30, and the pointer subtraction counts elements rather than bytes.
-
What does this program print on its second line?
#include <stdio.h> #define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) int main(void){ int values[7]={0}; printf("%zu\n", ARRAY_LEN(values)); return 0; }Answer
7
Seven ints occupy twenty-eight bytes, and dividing by the size of one int gives seven elements.
-
What does GCC warn about when
memcpyusessizeof sourceon a pointer?Answer
That the size is the pointer, not the array
sizeofon a pointer gives eight bytes, and GCC recognises the suspicious pattern with-Wsizeof-pointer-memaccess. -
What happens when
_Generichas no matching association and no default?Answer
The compiler reports an incompatible selector
The type of the controlling expression must match an association or a default, so the file does not compile.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev