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

2

5

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.

Underact answered 25/6, 2023 at 17:27 Comment(4)
"ceis" What is e doing there?Sorption
It's way more useful to post a program instead of snippets we have to assemble, i.e. minimal reproducible exampleOperetta
@Ehab Elsayed, you may find Formatted print without the need to specify type matching specifiers using _Generic interesting as it "prints any kind of format".Illaffected
@Sorption : 'e' format is edge case testingUnderact
S
3

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");
}
Sorption answered 25/6, 2023 at 18:19 Comment(0)
C
4

If you pass a va_list parameter to a function that calls va_arg, the calling function is no longer allowed to use that va_list (other than calling va_end). You violate this rule because print_all calls other functions that call va_arg.

One way to fix this would be to put all the va_arg calls in the same function. But this wouldn't work well with your function pointer approach.

Another way to fix it is to pass va_list * to the helper functions instead of va_list. This is explicitly allowed by the standard, and you can then keep using the same va_list in other functions. So you'd need to modify all your helper functions (and the function pointer type) to take a pointer.

C standard reference (C17 7.16/3):

The object ap [of type va_list] may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the value of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.257

Footnote 257:

It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.

Crash answered 25/6, 2023 at 17:43 Comment(1)
So, I can't use the va_arg function with a parameter of type va_list, if used like this the value of va_list object wouldn't be determined. Instead, I call it by reference in order to be determined. If I correctly got it or not, Thanks for your effortUnderact
S
3

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");
}
Sorption answered 25/6, 2023 at 18:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.