1

I'm totally new to C and I'm wondering is it possible to create a variadic function and pass pointers of variables into it and write data into the variables?

One obvious example of what I'm looking for is the scanf function which takes the input from the stdin and writes it into the variables.

Here is a sample of want I want to do:

void fun(int num, ...){
//    insert 2 in a and "abc" in b
}

int main(void){
    int a;
    char *b;
    fun(2, &a, &b);
}

update I can alter my constructor to get variables pattern instead of the number of them, so here is the code after modification:

void fun(char *fmt, ...){
//    insert 2 in a and "abc" in b
}

int main(void){
    int a;
    char *b;
    fun("dc", &a, &b);
}
Yashar
  • 2,455
  • 3
  • 25
  • 31
  • 1
    The thing is how to know the type of these variable. – llllllllll Mar 15 '18 at 21:55
  • 4
    I can assure you it's possible, but you will learn more if you work it out for youself. First, figure out how you would write this function if it _wasn't_ variadic. Then think about what you would have to change to make it variadic. Also, maybe you haven't discovered [`stdarg.h`](http://en.cppreference.com/w/c/variadic) yet? You need that. – zwol Mar 15 '18 at 21:56
  • 2
    Remember that `scanf()` and family have the format string to guide them as to what types are present in the variable argument list. On the face of it, your `2` in the call identifies two variables (pointers), but there is no type information. You need to know (somehow) the types of the pointers. (I learned to program in C on a machine where the `char *` value for a given memory location was different from the `int *` address for the same memory location — I am sensitive to problems in this area, even 30 years later.) You might decide to use a string instead of a count: `fun("i s", &a, &b)`. – Jonathan Leffler Mar 15 '18 at 22:31
  • Why don't you look at the source of `scanf`? – ack Mar 15 '18 at 23:17
  • @JonathanLeffler thanks for your comment, I update the question – Yashar Mar 16 '18 at 06:40
  • The [answer](https://stackoverflow.com/a/49313605/15168) by [Nominal Animal](https://stackoverflow.com/users/1475978/nominal-animal) does essentially what I suggested and meets your modified specification pretty well. – Jonathan Leffler Mar 16 '18 at 06:43
  • @JonathanLeffler I tried that but the problem is when I alter `s1` to some new value like `"hello"` in the `foo` function and print the value of `s1` in the main function after `foo`, it still prints `"First"`. they have different memory addresses – Yashar Mar 16 '18 at 06:56
  • Hmmm...the given answer is concerned with the printing side (accessing the variables in the variadic function) rather than the scanning side (modifying the variables in the calling code). If no-one else has done so, I'll do something in the morning — I need to sleep now. – Jonathan Leffler Mar 16 '18 at 06:59

1 Answers1

7

Start with the example code shown in the stdarg man page (man 3 stdarg). Slightly modified for readability, and adding a trivial main():

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>

void foo(char *fmt, ...)
{
   va_list ap;
   int d;
   char c, *s;

   va_start(ap, fmt);

   while (*fmt) {
       switch (*(fmt++)) {

       case 's':
           s = va_arg(ap, char *);
           printf("string %s\n", s);
           break;

       case 'd':
           d = va_arg(ap, int);
           printf("int %d\n", d);
           break;

       case 'c':
           /* need a cast here since va_arg only
              takes fully promoted types */
           c = (char) va_arg(ap, int);
           printf("char %c\n", c);
           break;
       }
   }

   va_end(ap);
}


int main(void)
{
    char *s1 = "First";
    char *s2 = "Second";
    int   d = 42;
    char  c = '?';

    foo("sdcs", s1, d, c, s2);

    return EXIT_SUCCESS;
}

If you compile and run the above, it will output

string First
int 42
char ?
string Second

As liliscent and Jonathan Leffler commented to the question, the key point is that we need a way to describe the type of each variadic argument. (In C, type information is essentially discarded at compile time, so if we want to support multiple types for one argument, we must also pass its type explicitly: the type (of a variadic function argument) simply does not exist at run time anymore.)

Above, the first parameter, fmt, is a string where each character describes one variadic argument (by describing its type). Thus, there are expected to be the same number of variadic arguments as there are s, d, or c characters in the fmt string.

The printf family of functions and the scanf family of functions both use % to indicate a variadic argument, followed by the formatting details and type specification of that argument. Because of the quite complex formatting they support, the code implementing those is much more complicated than the above example, but the logic is very much the same.


In an update to the question, OP asked if the function can change the value of the variadic arguments -- or rather, the values pointed to by the variadic arguments, similar to how scanf() family of functions work.

Because parameters are passed by value, and va_arg() yields the value of the parameter, and not a reference to the parameter, any modifications we make to the value itself locally (to s, d, or c in the foo() function example above) will not be visible to the caller. However, if we pass pointers to the values -- just like scanf() functions do --, we can modify the values the pointers point to.

Consider a slightly modified version of the above foo() function, zero():

void zero(char *fmt, ...)
{
   va_list ap;
   int *d;
   char *c, **s;

   va_start(ap, fmt);

   while (*fmt) {
       switch (*(fmt++)) {

       case 's':
           s = va_arg(ap, char **);
           if (s)
               *s = NULL;
           break;

       case 'd':
           d = va_arg(ap, int *);
           if (d)
               *d = 0;
           break;

       case 'c':
           /* pointers are fully promoted */
           c = va_arg(ap, char *);
           if (c)
               *c = 0;
           break;
       }
   }

   va_end(ap);
}

Note the differences to foo(), especially in the va_arg() expressions. (I would also suggest renaming d, c, and s to dptr, cptr, and sptr, respectively, to help remind us humans reading the code that they are no longer the values themselves, but pointers to the values we wish to modify. I omitted this change to keep the function as similar to foo() as possible, to keep it easy to compare the two functions.)

With this, we can do for example

    int d = 5;
    char *p = "z";

    zero("ds", &d, &p);

and d will be cleared to zero, and p to be NULL.

We are not limited to a single va_arg() within each case, either. We can, for example, modify the above to take two parameters per formatting letter, with the first being a pointer to the parameter, and the second the value:

void let(char *fmt, ...)
{
   va_list ap;
   int *dptr, d;
   char *cptr, c, **sptr, *s;

   va_start(ap, fmt);

   while (*fmt) {
       switch (*(fmt++)) {

       case 's':
           sptr = va_arg(ap, char **);
           s = va_arg(ap, char *);
           if (sptr)
               *sptr = s;
           break;

       case 'd':
           dptr = va_arg(ap, int *);
           d = va_arg(ap, int);
           if (dptr)
               *dptr = d;
           break;

       case 'c':
           cptr = va_arg(ap, char *);
           /* a 'char' type variadic argument
              is promoted to 'int' in C: */
           c = (char) va_arg(ap, int);
           if (cptr)
               *cptr = c;
           break;
       }
   }

   va_end(ap);
}

This last function you can use via e.g.

int a;
char *b;

let("ds", &a, 2, &b, "abc");

which has the same effect as a = 2; b = "abc";. Note that we do not modify the data b points to; we just set b to point to a literal string abc.


In C11 and later, there is a _Generic keyword (see e.g. this answer here), that can be used in conjunction with preprocessor macros, to choose between expressions depending on the type(s) of the argument(s).

Because it does not exist in earlier versions of the standards, we now have to use for example sin(), sinf(), and sinl() to return the sine of their argument, depending on whether the argument (and desired result) is a double, float, or a long double. In C11, we can define

#define Sin(x) _Generic((x),               \
                        long double: sinl, \
                        float: sinf,       \
                        default: sin)(x)

so that we can just call Sin(x), with the compiler choosing the proper function variant: Sin(1.0f) is equivalent to sinf(1.0f), and Sin(1.0) is equivalent to sin(1.0), for example.

(Above, the _Generic() expression evaluates to one of sinl, sinf, or sin; the final (x) makes the macro evaluate to a function call with the macro parameter x as the function parameter.)

This is not a contradiction to the earlier section of this answer. Even when using the _Generic keyword, the types are checked at compile time. It is basically just syntactic sugar on top of macro parameter type comparison checking, that helps writing type-specific code; in other words, a kind of a switch..case statement that acts on preprocessor macro parameter types, calling exactly one function in each case.

Furthermore, _Generic does not really work with variadic functions. In particular, you cannot do the selection based on any variadic arguments to those functions.

However, the macros used can easily look like variadic functions. If you want to explore such generics a bit further, see e.g. this "answer" I wrote some time ago.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86