carl.hiass
carl.hiass

Reputation: 1784

Using va_args without passing num (like printf)

The following is the most basic example I can come up with to pass variable-length args to a function:

int printme(int num, ...)
{
    va_list ap;
    va_start(ap, num);
    for (int i = 0; i < num; ++i) {
        char *arg = va_arg(ap, char *);
        printf("%d. %s\n", i + 1, arg);
    }
    va_end(ap);
    return 1;
}

int main(void)
{
    printme(2, "X", "YY");
}

However, notice that I am passing in the length as the first argument (or as any argument). Is it possible to use these va_ macros with something like a printf-ish function? For example, could I do something like this (without passing the number of args?

print2("Hello something %s %s", "Arg 1", "Arg 2");
print2("Hello something %s %s %s", "Arg 1", "Arg 2", "Arg 3");

If so, what would the function to receive that look like? And if it's not possible, how does printf implement it then?

Upvotes: 4

Views: 1286

Answers (3)

tstanisl
tstanisl

Reputation: 14157

You could use macroprocessor to count arguments.

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

int printme(int num, ...)
{
    va_list ap;
    va_start(ap, num);
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    return 1;
}

#define TENTH(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,...) p10
#define NARGS(...) TENTH(__VA_ARGS__,9,8,7,6,5,4,3,2,1,0)
#define printme(...) printme(NARGS(__VA_ARGS__), __VA_ARGS__)

int main(void)
{
    printme("X", "YY");
}

How it works:

Macro TENTH simply returns the tenth argument Example:

TENTH(a,b,c,d,e,f,g,h,i,j,k)

expands as j.

It can used to obtain a number of argument:

                               | tenth argument
                               v
TENTH("X", "XY", 9,8,7,6,5,4,3,2,1,0)

expand as 2 because 2 is the tenth argument of TENTH.

Similarly,

                                    | tenth argument
                                    v
TENTH("A", "B", "C", "D", 9,8,7,6,5,4,3,2,1,0)

expands as 4.

Macro NARGS places its arguments before sequence 9,8,... shifting the sequence to get the number of arguments.

I've used printme to avoid polluting namespace.Whenever a macro is expand is get disabled to avoid infinite recursion. It will not expand any further becaming a call to original printme(). The first argument num is computed with NARGS macro. All remaining arguments are passed after the num one.

There are still some disadvantages:

  • works up to ten arguments, but this limitation can be easily lifted
  • requires at least one argument (it's possible to workaround but it is complex)

Upvotes: 2

carl.hiass
carl.hiass

Reputation: 1784

Here's a basic example of detecting the number of arguments from a parsed string. It's not taking into account any special circumstances and doesn't do anything with actually formatting/parsing the strings, but shows a basic working program to show how the count can be parsed from a string:

int printmy(char * format, ...)
{
    // num args
    int num = 0;
    for(char idx=0, c; c=format[idx]; idx++) {
        // ignore escape char + double-percent
        if (c=='\\' || (c=='%' && format[idx+1]=='%'))
            idx++;
        else if (c=='%')
            num++;
    }

    // print variable args
    va_list ap;
    va_start(ap, format);   // need to give it the last argument before the "..."
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    
    // return num args received
    return num;
}

int main(void)
{
    int num_args = printmy("Hello something \% \\% %% %s %s %s", "Arg 1", "Arg 2", "Arg 3");    
    printf("Num Args Parsed: %d\n", num_args);
}

And we get:

1. Arg 1
2. Arg 2
3. Arg 3
4.
Num Args Parsed: 4

Upvotes: 3

Amal K
Amal K

Reputation: 4929

If you only want to pass strings, it can be done quite easily by writing a function to parse the first argument like countargs(char*) that I've written here. It returns the number of arguments:

#include <stdarg.h>
#include <stdio.h>
int printme(char* fmt, ...)
{
    va_list ap;
    int num = countargs(fmt);
    va_start(ap, num);
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    return 1;
}

int countargs(char* fmt)
{
    int i, num = 0;
    if(strlen(fmt) < 2) 
    { 
        return 0;
    }
    for(i = 0; fmt[i+1] != '\0'; i++)
    {
        if (fmt[i] == '%' && fmt[i+1] == 's')
        {
            num++;
        }
    }
    return num;
}

int main(void)
{
    printme("%s%s", "Stack", "Overflow");
}

Upvotes: 3

Related Questions