Reputation: 11
I'm trying to set up an array of functions that takes in functions that need different variables (for example, my_putchar
needs a char
while my_put_nbr
needs an int
), so I just decided to define it using a void *
. While it does work it gives me warnings when I compile.
#include "stdarg.h"
#include "my.h"
int put_perc(void)
{
my_putchar('%');
}
int read_print(const char *s, int i, va_list args)
{
int k = 0;
char guide[] = "cdis%f";
int (*functions[])(void *) = {my_putchar, my_put_nbr,
my_put_nbr, my_putstr, put_perc, my_put_float};
for (k = 0; s[i + 1] != guide[k]; k++);
functions[k](va_arg(args, void *));
}
void mini_printf(const char *s, ...)
{
int k = 0;
va_list args;
char guide[] = "cdis%";
va_start(args, s);
for (int i = 0; i < my_strlen(s); i++) {
if (s[i] == '%') {
read_print(s, i, args);
i++;
} else {
my_putchar(s[i]);
}
}
va_end(args);
}
void main(void)
{
mini_printf("%s %s %s letter is %c followed by %d and %i.\nfloats %f \nlets also test this out, %s 100%%\n", "come", "on", "WORK", 'h', 58, 12, 55.756567575658, "this should work");
}
These are the warnings I get.
my_printf.c: In function ‘read_print’:
my_printf.c:20:35: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘void (*)(char)’ [-Wincompatible-pointer-types]
20 | int (*functions[])(void *) = {my_putchar, my_put_nbr,
| ^~~~~~~~~~
my_printf.c:20:35: note: (near initialization for ‘functions[0]’)
my_printf.c:20:47: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
20 | int (*functions[])(void *) = {my_putchar, my_put_nbr,
| ^~~~~~~~~~
my_printf.c:20:47: note: (near initialization for ‘functions[1]’)
my_printf.c:21:9: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
21 | my_put_nbr, my_putstr, put_perc, my_put_float};
| ^~~~~~~~~~
my_printf.c:21:9: note: (near initialization for ‘functions[2]’)
my_printf.c:21:21: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘int (*)(const char *)’ [-Wincompatible-pointer-types]
21 | my_put_nbr, my_putstr, put_perc, my_put_float};
| ^~~~~~~~~
my_printf.c:21:21: note: (near initialization for ‘functions[3]’)
my_printf.c:21:32: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘int (*)(void)’ [-Wincompatible-pointer-types]
21 | my_put_nbr, my_putstr, put_perc, my_put_float};
| ^~~~~~~~
my_printf.c:21:32: note: (near initialization for ‘functions[4]’)
my_printf.c:21:42: warning: initialization of ‘int (*)(void *)’ from incompatible pointer type ‘int (*)(double)’ [-Wincompatible-pointer-types]
21 | my_put_nbr, my_putstr, put_perc, my_put_float};
| ^~~~~~~~~~~~
my_printf.c:21:42: note: (near initialization for ‘functions[5]’)
I don't really know how to fix this because I just thought void *
acted as a catchall for variable types. (this is only my 3rd week of coding so im still not that experienced)
Upvotes: 1
Views: 98
Reputation: 70372
I just thought void * acted as a catchall for variable types.
Not really, no. What is true is that any object pointer type can be converted into a void *
and back. (Back into the original pointer type, mind you. Taking the X* -> void* -> Y* route is not allowed.)
But this here...
int (*functions[])(void *)
...is an array of pointers to functions returning int
and taking void *
as an argument.
Your actual functions, however, are taking non-pointer types (char
, int
, void
, double
), or a const pointer (const char *
). Neither of those can be converted into a void *
without a cast.
And that would be only the beginning of your trouble. Your put_perc
, for example, doesn't take an argument. You'd be calling it as a function taking an argument, though, passing it the next void *
from the stack... I see all kinds of "funny" things about to happen with this construct of yours.
So you could have a void * functions[]
and put the function pointers in there?
No you can't, because it's only object pointers (i.e., int *
, double *
etc.) which can be turned into void *
and back. Function pointers cannot.
Instead of calling read_print
and expect it to "automagically function" given generic arguments, I would suggest your mini_printf
to read the conversion specifier ('c'
, 's'
, 'd'
...) following the '%'
in the format string, switch
on that, and do the right thing in the respective case
s, like printing a '%'
or taking an int
of the stack and passing it to my_put_nbr
. Much, much cleaner that way.
Upvotes: 3
Reputation: 181199
I just thought
void *
acted as a catchall for variable types.
This is not remotely true. void *
is a specific type, not some kind of wildcard. Pointer-to-object types can be converted to void *
and back, and those conversions are automatic under most circumstances. Integers can be explicitly converted to void *
and vice versa, though the language spec does not not guarantee that a round-trip of such conversions is value-preserving. No conversions are even defined by the spec between void *
and other types, such as floating-point types.
Function pointer types are distinguished from each other both by the return type of the pointed-to function and by the parameter types of that function. The type of a pointer to a function accepting an int
parameter is different from the type of a pointer to a function accepting a double
parameter, for example. You can convert explicitly among different function pointer types, via cast, but the language does not define any automatic conversions among these. That's what your diagnostics are trying to tell you.
You can solve your particular problem by casting the function pointers to a common type. For this purpose, it is convenient to declare a typedef
alias for that function-pointer type, as otherwise the required code becomes very difficult to read, very quickly. For example,
typedef void (*common_function_pointer)(void);
// ...
common_function_pointer functions[] = {
(common_function_pointer) my_putchar,
(common_function_pointer) my_put_nbr,
(common_function_pointer) my_put_nbr,
(common_function_pointer) my_putstr,
(common_function_pointer) put_perc,
(common_function_pointer) my_put_float
};
But this doesn't actually help you very much, because you need to convert those pointers back to the correct function-pointer types in order to call the pointed-to functions, which means you need to know those types, which in this case means you need to know what function each pointer actually points to. You might as well call the functions directly, then.
Upvotes: 3