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.

4
  • "ceis" What is e doing there? Commented Jun 25, 2023 at 17:30
  • 2
    It's way more useful to post a program instead of snippets we have to assemble, i.e. minimal reproducible example Commented Jun 25, 2023 at 17:30
  • @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". Commented Jun 25, 2023 at 19:15
  • @KamilCuk : 'e' format is edge case testing Commented Jun 25, 2023 at 19:30

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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 effort
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");
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.