(ab)Using function pointers and void pointers to achieve truly generic functions in C
Jul 9th, 2010 by Adam
This has got to be some of the strangest code I have ever written. I was recently working on my implementation of a Finite State Machine engine in C, when I decided that I really wanted a way to pass both a function pointer (easy), and a list of arguments that could be passed to that function (more difficult), as part of a table (really just an array) describing what functions, and what arguments to pass (really hard).
I toyed with ideas like variadic macros (or functions), abusing some sort of global stack of void pointers to various types of structs, and a bunch of other ideas. Finally, I came up with the following code (an example of the technique):
#include <stdio.h>
struct threeint_args {
int a;
int b;
int c;
};
void mult(void *args)
{
struct threeint_args *ma = (struct threeint_args*)args;
printf("%d * %d * %d = %d\n",
ma->a, ma->b, ma->c,
ma->a * ma->b * ma->c);
}
void add(void *args)
{
struct threeint_args *aa = (struct threeint_args*)args;
printf("%d + %d + %d = %d\n",
aa->a, aa->b, aa->c,
aa->a + aa->b + aa->c);
}
struct generic_func {
void(*func)(void*);
void *args;
};
void run_generics(struct generic_func gfs[])
{
struct generic_func *gf = gfs;
while(gf->func != NULL) {
gf->func(gf->args);
gf++;
}
}
int main(int argc, char **argv)
{
struct generic_func examples[] =
{
{mult, (void*)&(struct threeint_args){1, 2, 3}},
{add, (void*)&(struct threeint_args){1, 2, 3}},
{mult, (void*)&(struct threeint_args){4, 5, 6}},
{add, (void*)&(struct threeint_args){4, 5, 6}},
{mult, (void*)&(struct threeint_args){7, 8, 9}},
{add, (void*)&(struct threeint_args){7, 8, 9}},
{NULL}
};
run_generics(examples);
}
“Alright”, I thought to myself, “this little program probably will throw a fit if I try to compile it, but lets try anyway.”
$ gcc -ggdb -o test test.c $
“Now wait a minute – no complaints? Certainly, if I try to run it, there will be some sort of segfault, or bug, or something. This can’t be right…”
$ valgrind ./test ==16546== Memcheck, a memory error detector ==16546== Copyright (C) 2002-2009, and GNU GPLd, by Julian Seward et al. ==16546== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info ==16546== Command: ./test ==16546== 1 * 2 * 3 = 6 1 + 2 + 3 = 6 4 * 5 * 6 = 120 4 + 5 + 6 = 15 7 * 8 * 9 = 504 7 + 8 + 9 = 24 ==16546== ==16546== HEAP SUMMARY: ==16546== in use at exit: 0 bytes in 0 blocks ==16546== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==16546== ==16546== All heap blocks were freed -- no leaks are possible ==16546== ==16546== For counts of detected and suppressed errors, rerun with: -v ==16546== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
Holy s**t. That just worked.
I don’t often get surprised by code that I write. After all, if I am writing it, it means I understand it (most of the time). This example; however, completely flabbergasted me. Being able to store the pointer to a function, and a pointer to the arguments to the function in an array means I can do all sorts of things. I could develop a signals and slots mechanism, I can pass table-local contexts to function pointers, hell, I could rule the world. I couldn’t find anything else like this on the web – so I thought I would share!