4

Using the <stdarg.h> header, one can make a function that has a variable number of arguments, but:

  1. To start using a va_list, you need to use a va_start macro that needs to know how many arguments there, but the printf & ... that are using va_list don't need the argument count. How can I create a function that doesn't need the argument count like printf?

  2. Let's say I want to create a function that takes a va_list and instead of using it, passes it to another function that requires a va_list? (so in pseudocode, it would be like void printfRipOff(const char* format, ...) {printf(format, ...);})

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    `va_start macro that needs to know how many arguments there` `va_start` macro does not need to know how many arguments there are. `How can I create a function that doesn't need the argument count like printf?` `printf` "knows" the argument count - it counts the number of `%` in the format string not followed by another `%`. `say I want to create a function that takes a va_list & instead of using it, passes it to another function that requires a va_list?` Let's say that, and? So write that function. – KamilCuk Oct 06 '22 at 18:09
  • Those are specific to `printf`. What I was after was `sprintf`. – KianFakheriAghdam Oct 06 '22 at 18:16
  • 2
    `sprintf` and `printf` are identical except that `printf` prints to `stdout` and `sprintf` prints to the `char*` passed as the first argument – Willis Hershey Oct 06 '22 at 18:17
  • 1
    The argument count in `printf` is implied by the format string. – user16217248 Oct 06 '22 at 18:18
  • 1
    There are two requirements: (1) A varargs function must have at least one "fixed" argument, since you need to be able to pass the last one to `va_start`. (2) The function must be able to determine from the fixed arguments, somehow, how many more variable arguments to expect. That doesn't mean there's necessarily an explicit count; usually there's not. There are a variety of strategies used by (a) the `*printf` family, (c) the `execl*` family, (c) [`open`](https://linux.die.net/man/2/open), and others. – Steve Summit Oct 06 '22 at 18:25
  • 2
    See also [question 15.4](https://c-faq.com/varargs/varargs1.html) in the [C FAQ list](https://c-faq.com/). [Question 15.5](https://c-faq.com/varargs/vprintf.html) and the rest of [section 15](https://c-faq.com/varargs/index.html) may be of interest to you, also. – Steve Summit Oct 06 '22 at 18:27
  • 1
    Hi, have you maybe seen a function like `int sum(int count, ...)` that says `va_start(args,count)` or something like that. The `va_start()` macro needs to know the last argument not in the variadic list. It doesn't need to know the count as such. But you do need there to be some way for the function to figure-out how many arguments there are. A format like `prinf()` or a count are good answers. Sentinel values are never recommended. – Persixty Oct 06 '22 at 18:37
  • 2
    If what you _wanted_ was provided by a person who also offered an answer, you should consider clicking the hollow check-mark to indicate that you have accepted the answer. I would also suggest up-clicking any other answers that you found helpful. – ryyker Oct 06 '22 at 18:38
  • 2
    Rather than putting "thanks" in the question, you should [accept an answer](https://stackoverflow.com/help/accepted-answer). – dbush Oct 06 '22 at 18:39
  • 1
    @Persixty "Sentinel values are never recommended." Citation needed. :-) – Steve Summit Oct 06 '22 at 18:39
  • 1
    @SteveSummit It's difficult to find citations of non-recommendation. The classic use is the NUL terminator of C-style strings. Probably the biggest blunder in the language. – Persixty Oct 06 '22 at 18:43
  • 1
    @Persixty Ah, okay, so it's not "Sentinel values are never recommended", it's, "Some programmers don't like sentinel values". I mean, in certain circumstances, using sentinel values is more than just recommended, it's *required:* such as when you're calling `execl`. – Steve Summit Oct 06 '22 at 18:46
  • 1
    @SteveSummit It's not a matter of like or dislike. They're poor design. By definition they either break the type system or exclude edge cases and in all circumstances there are better alternatives. I suppose you may find people who recommend them but not people you should listen to. And yes, there are poorly designed APIs where they're required. But the point here is about design not use. – Persixty Oct 06 '22 at 18:54
  • 1
    Arguing over if a sentinel value is bad practice or not when used in variadic functions is like arguing over if it's best to replace all your programming staff with dromedaries or camels. Don't do such a thing to begin with! They are dangerous, they take up lots of space, they chew up anything handed to them, they crap all over the place, they might crash the Windows and there's no reason why you would ever use them. Variadic functions aside, the same things could probably be said about replacing your dev team with dromedaries/camels too. – Lundin Oct 18 '22 at 14:28

3 Answers3

3

Let's say I want to create a function that takes a va_list and instead of using it, passes it to another function that requires a va_list?

Look at function vprintf( const char * format, va_list arg ); for one example of a function that takes a va_list as an input parameter.

It is basically used this way:

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

void CountingPrintf(const char* format, ...)
{
   static int counter = 1;
   printf("Line # %d: ", counter++); // Print a Counter up front

   va_list args;
   va_start(args, format); // Get the args
   vprintf(format, args);  // Pass the "args" through to another printf function.

   va_end(args);
}

int main(void) {
    CountingPrintf("%s %s\n", "Hello", "World");
    CountingPrintf("%d + %d == %d\n", 2, 3, 2+3);
    
    return 0;
}

Output:

Line # 1: Hello World
Line # 2: 2 + 3 == 5
abelenky
  • 63,815
  • 23
  • 109
  • 159
2
  1. Functions like printf() and scanf() have the format string argument that tells them the number and types of the arguments that must have been provided by the function call.

    If you're writing your own function, you must have some way of knowing how many arguments were provided and their types. It may be that they are all the same type. It may be that they're all pointers and you can use a null pointer to indicate the end of the arguments. Otherwise, you probably have to include a count.

  2. You can do that as long as provided the called function expects a va_list. There are caveats (see C11 §7.16 Variable arguments) but they're manageable with a little effort.

A very common idiom is that you have a function int sometask(const char *fmt, ...) and a second function int vsometask(const char *fmt, va_list args), and you implement the first as a simple call to the second:

int sometask(const char *fmt, ...)
{
    va_list args;
    va_start(fmt, args);
    int rc = vsometask(fmt, args);
    va_end(args);
    return rc;
}

The second function does the real work, of course, or calls on other functions to do the real work.


In the question, you say:

va_start macro that needs to know how many arguments …

No; the va_start macro only needs to know which argument is the one before the ellipsis — the argument name before the , ... in the function definition.


In a comment, you say:

This is what I'm trying to write, but it didn't work.

string format(const char* text, ...)
{
    // temporary string used for formatting
    string formattedString;
    initializeString(&formattedString, text);
    // start the va_list
    va_list args;
    va_start(text, args);
    // format
    sprintf(formattedString.array, text, args);
    // end the va_list
    va_end(args);
    return formattedString;
}

As noted by abelenky in a comment, you need to use vsprintf():

string format(const char* text, ...)
{
    string formattedString;
    initializeString(&formattedString, text);
    va_list args;
    va_start(text, args);
    vsprintf(formattedString.array, text, args);
    va_end(args);
    return formattedString;
}

That assumes that the formattedString has enough space in the array for the formatted result. It isn't immediately obvious how that's organized, but presumably you know how it works and it works safely. Consider using vsnprintf() if you can determine the space available in the formattedString.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • this is what I'm trying to write, didn't work. `string format(const char* text, ...) { // temporary string used for formatting string formattedString; initializeString(&formattedString, text); // start the va_list va_list args; va_start(text, args); // format sprintf(formattedString.array, text, args); // end the va_list va_end(args); return formattedString; }` – KianFakheriAghdam Oct 06 '22 at 18:25
  • 2
    You need to be calling `vsprintf` instead of `sprintf`. The `v` prefix means "Takes a ***V***ariable number of arguments." – abelenky Oct 06 '22 at 18:28
  • 2
    @KianFakheriAghdam - That comment would be a great addition to your post, thus explaining what you tried, and ask for pointers on why it did not work. Please consider editing your post to add that content. Be sure to include what precisely you are using for a compiler and what libraries. It looks like you may be studying `cs50` as `string` is not a native type in `C`. `C` would of course use `char *` in place of `string`. – ryyker Oct 06 '22 at 18:34
  • I will upvote y'all answers. I am new here & don't really know how things work. also the `string` is actually a struct made by myself, I'm creating a C library that has a ton of nice features & that string is a run-time `malloc` string. – KianFakheriAghdam Oct 07 '22 at 05:13
2

The printf and scanf family of functions don't need to be explicitly passed the length of the va_list because they are able to infer the number of arguments by the format string.

For instance, in a call like:

printf("%d %c %s\n", num, c, "Hello");

printf is able to examine the first parameter, the format string, and see that it contains a %d, a %c, and a %s in that order, so it can assume that there are three additional arguments, the first of which is a signed int, the second is a char and the third is a char* that it may assume is a pointer to a null-terminated string.

To write a similar function that does not need to be explicitly told how many arguments are being passed, you must find a way to subtly give it some information allowing it to infer the number and types of the arguments in va_list.

Willis Hershey
  • 1,520
  • 4
  • 22