Making a varargs print fn for "c", "f", "i", "s" that doesn't work

Problem

I have created a struct to store a format character and a pointer to the function which prints according to the formatter:

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

I have created functions to print 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 have 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);
}

When I compile and test the code below:

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

It prints B, 66 instead of B, 3, stSchool.

I believe the problem is with va_arg in each function, but I lack the knowledge of variadic functions to fix it. I do not want to use switch cases to modularize my program.

Problem

I have created a fmt struct to store format characters and a pointer to the function which prints according to the format:

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

I have also created functions to print 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 have created an array of the fmt 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);
}

When I compile and test the code below:

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

It prints B, 66 instead of B, 3, stSchool.

I believe the problem is with va_arg in each function, but I lack the knowledge of variadic functions to fix it. I do not want to use switch cases to modularize my program.

The problem lies in the print_all function. The issue is that va_arg retrieves the next argument each time it is called. However, in the current implementation, va_arg is called multiple times in the inner loop, which causes the arguments to be consumed incorrectly.

To fix this issue, you need to make a copy of the va_list before entering the inner loop. Here’s the modified code for the print_all function:

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, args_copy; // Add args_copy variable
  va_start(args, format);

  if (format == NULL)
  {
    return;
  }

  va_copy(args_copy, args); // Make a copy of args

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

With this modification, the program should now output B, 3, stSchool as expected.