The Hollow Owl
The Hollow Owl

Reputation: 11

Getting warnings for using void * while trying to recode printf

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

Answers (2)

DevSolar
DevSolar

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 cases, like printing a '%' or taking an int of the stack and passing it to my_put_nbr. Much, much cleaner that way.

Upvotes: 3

John Bollinger
John Bollinger

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

Related Questions