I have to register different callback functions to a scheduler. The callback signature defines a void *
parameter. Some callbacks don't use a parameter. This works, but is it clean?
I expected at least an incompatible pointer type warning for the missing cast on the parameterless call which I actually get when using a function to register the callback but the compiler doesn't complain about this example.
#include <stdio.h>
typedef void (* cb)(void *);
void noparams(void) {
printf("noparams\n");
}
void params(void * param) {
char * s = param;
printf(s);
}
void call(cb cbk, void * ctx) {
cbk(ctx);
}
int main(void) {
void * context = 0;
call(noparams, context); // warning expected
char str[] = "params\n";
call(params, str);
return 0;
}
1 Answer 1
When I compile your code with gcc (9.1.1 ) and use the command line option -Wall, I get a warning:
cb.c:20:10: warning: passing argument 1 of ‘call’ from incompatible pointer type [-Wincompatible-pointer-types]
20 | call(noparams, context); // warning expected
| ^~~~~~~~
| |
| void (*)(void)
So you might want to up the warning level on the compilation. You could suppress this warning with a cast -- it is ok to cast function pointers.
However the real problem is that it is, I believe, undefined behaviour to call a function (either directly or through a function pointer) with a different number of arguments from those given in its definition. (You might want to check this by asking a stack overflow question with tags C and language lawyer).
If it is indeed UB then you have to avoid it. It might appear to work with a particular compiler, but fail arbitrarily with a different compiler, or even with the same compiler on a different platform.
What you have to do, I think, is to provide a callback with the demanded arguments. You could do this, for example, by adding
void noparams_wrap(void* p) {
noparams();
}
and passing noparams_wrap to call() rather than noparams() itself.
-
\$\begingroup\$ Thank you for the answer. I'll ask on SO. The problem with the wrapper function is that the compiler complains about the unused parameter. Still better than UB though. I wonder if there's an elegant solution. I use my example on ARM and it probably works because the unused parameter ends up in the scratch register r0 which can be safely ignored/overwritten by the callee. I have no clues on why it works on x86_64 (ideone). I don't know the calling conventions for that arch. \$\endgroup\$robsn– robsn2020年09月25日 09:51:55 +00:00Commented Sep 25, 2020 at 9:51