Search code examples
cstructprintffunction-pointersvariadic-functions

Creating a variadic function that prints any kind of format of ("c", "f", "i", "s"), but doesn't work


I've created a struct, that groups the format character and a pointer to the function which prints according to the formatter.

typedef struct formatter
{
  char spec;
  void (*print)(va_list);
} fmt;

Then, i've created functions that prints each format.

void print_char(va_list args)
{
  printf("%c", va_arg(args, int));
}

void print_int(va_list args)
{
  printf("%d", va_arg(args, int));
}

void print_float(va_list args)
{
  printf("%f", va_arg(args, double));
}

void print_string(va_list args)
{
  char *spec = va_arg(args, char *);
  if (spec == NULL)
  {
    printf("(nil)");
    return;
  }
  printf("%s", spec);
}

in the main variadic function i've created an array of the struct in order to loop over it.

void print_all(const char *const format, ...)
{
  fmt f[] = {
      {'c', print_char},
      {'i', print_int},
      {'f', print_float},
      {'s', print_string},
      {'\0', NULL}};

  int i = 0, j;
  char *separator = "";
  va_list args;
  va_start(args, format);

  if (format == NULL)
  {
    return;
  }

  while (format[i] != '\0')
  {
    j = 0;
    while (f[j].spec)
    {
      if (f[j].spec == format[i])
      {
        printf("%s", separator);
        f[j].print(args);
        separator = ", ";
        break;
      }
      j++;
    }
    i++;
  }
  printf("\n");
  va_end(args);
}

The problem came, when i compile the program and test the case below:

int main(void)
{
  print_all("ceis", 'B', 3, "stSchool");
  return (0);
}

It prints B, 66, Instead of printing B, 3, stSchool

I need to know where the problem at.

I expect the problem becomes in va_arg in each function, but in order of the less detailed knowledge that i've of variadic functions, i can't change something. and also i don't wanna implement it with the use of switch cases, in order to modularize my program.


Solution

  • Passing va_list by value to multiple function is invalid. You have to pass a pointer to va_list.

    #include <stdio.h>
    #include <stdarg.h>
    
    typedef struct formatter {
      char spec;
      void (*print)(va_list*);
    } fmt;
    
    void print_char(va_list *args) {
      printf("%c", va_arg(*args, int));
    }
    
    void print_int(va_list *args) {
      printf("%d", va_arg(*args, int));
    }
    
    void print_float(va_list *args) {
      printf("%f", va_arg(*args, double));
    }
    
    void print_string(va_list *args) {
      char *spec = va_arg(*args, char *);
      if (spec == NULL) {
        printf("(nil)");
        return;
      }
      printf("%s", spec);
    }
    
    void print_all(const char *const format, ...) {
      fmt f[] = {
          {'c', print_char},
          {'i', print_int},
          {'f', print_float},
          {'s', print_string},
          {'\0', NULL}};
      const char *separator = "";
      va_list args;
      va_start(args, format);
    
      if (format == NULL) {
        return;
      }
    
      for (int i = 0; format[i] != '\0'; ++i) {
        for (int j = 0; f[j].spec; f++) {
          if (f[j].spec == format[i]) {
            printf("%s", separator);
            f[j].print(&args);
            separator = ", ";
            break;
          }
        }
      }
      printf("\n");
      va_end(args);
    }
    
    int main(void) {
      print_all("cis", 'B', 3, "stSchool");
    }