ensure function pointer / argument compatibility for apply-like functions
Matthias Pfaller <[email protected]> Tue, 9 Dec 2025 11:32:13 +0100
| Newsgroups | gmane.comp.gcc.help |
|---|---|
| Organization | marco Systemanalyse und Entwicklung GmbH |
| Message-ID | <[email protected]> |
and now for something completely different...
In our RTOS I have a function called task_create. Originally I declared it as follows:
struct task *task_create(const char *name, void (*f)(void *), void *farg, unsigned
int stacksize, unsigned int priority);
When the task is created, it is started by calling f(farg). But actually I wanted to
be able to declare the task function taking an arbitrary typed argument and I wanted
to make sure that the "farg" argument (which also should be of an arbitrary type)
passed to task_create matches the prototype of f.
I came up with the following solution. I modified the original prototype as follows:
union __attribute__((__transparent_union__)) __task_arg {
void *__parg;
const void *__cparg;
volatile void *__vparg;
const volatile void *__cvparg;
int __iarg;
unsigned int __uiarg;
short __siarg;
unsigned short __usiarg;
};
struct task *_task_create(const char *name, void *f, union __task_arg arg, unsigned
int stacksize, unsigned int priority);
The task_create function is then replaced by the following macro:
static void __attribute__((error("incompatible arguments"), __unused__,
__noinline__)) __task_create_error(void) { while (1); }
/*
* We need to cast arg to unsigned int in the first if-clause
* because otherwise gcc will emit a
* warning when arg is something like &variable (object addresses never
* can be NULL).
*/
#define task_create(name, func, arg, stacksize, priority) ({ \
if (__builtin_constant_p(arg) && ((unsigned int)(arg)) == 0) { \
/* null pointer arg is allowed */ \
} else if ((__builtin_types_compatible_p(__typeof__(&func), void (*)(void
*)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(volatile void *))) && \
!__builtin_types_compatible_p(__typeof__(arg), signed char) && \
!__builtin_types_compatible_p(__typeof__(arg), char) && \
!__builtin_types_compatible_p(__typeof__(arg), short) && \
!__builtin_types_compatible_p(__typeof__(arg), int) && \
!__builtin_types_compatible_p(__typeof__(arg), long) && \
!__builtin_types_compatible_p(__typeof__(arg), unsigned char) && \
!__builtin_types_compatible_p(__typeof__(arg), unsigned short) && \
!__builtin_types_compatible_p(__typeof__(arg), unsigned int) && \
!__builtin_types_compatible_p(__typeof__(arg), unsigned long)) { \
/* every pointer is accepted when the function expects void * */ \
} else if ((__builtin_types_compatible_p(__typeof__(func), void (*)(signed
char)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(char)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(short)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(int)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(long)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(unsigned
char)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(unsigned
int)) || \
__builtin_types_compatible_p(__typeof__(func), void (*)(unsigned
long)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(signed char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(short)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(int)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(long)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned char)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned int)) || \
__builtin_types_compatible_p(__typeof__(&func), void (*)(unsigned long))) && \
(__builtin_types_compatible_p(__typeof__(arg), signed char) || \
__builtin_types_compatible_p(__typeof__(arg), char) || \
__builtin_types_compatible_p(__typeof__(arg), short) || \
__builtin_types_compatible_p(__typeof__(arg), int) || \
__builtin_types_compatible_p(__typeof__(arg), long) || \
__builtin_types_compatible_p(__typeof__(arg), unsigned char) || \
__builtin_types_compatible_p(__typeof__(arg), unsigned short) || \
__builtin_types_compatible_p(__typeof__(arg), unsigned int) || \
__builtin_types_compatible_p(__typeof__(arg), unsigned long))) { \
/* all integer arguments are considered compatible */ \
} else if (__builtin_types_compatible_p(__typeof__(func), void
(*)(__typeof__(arg)))) { \
/* matching type */ \
} else if (__builtin_types_compatible_p(__typeof__(&(func)), void
(*)(__typeof__(arg)))) { \
/* matching type */ \
} else { \
__task_create_error(); \
} \
_task_create(name, func, arg, stacksize, priority); \
})
That way I can e.g. do
void task1(int wd);
task_create("wd", task1, 20, 1024, 10);
But if I would do:
task_create("wd", task1, "hello", 1024, 10);
I would get a compile time error.Is there a simpler way to achieve my goal (I did
this about 14 years ago...)?
regards, Matthias