Search code examples
cvariadic

Null-terminating a list of arguments for a C variadic function


I'm messing around with variadic functions in C to learn how they work, and am trying to build a simple 'print lines' function without requiring manual counting of the lines. I'm doing this by wrapping the function in a macro that adds a null pointer to the end of a list of char * arguments, so the function can print line-by-line until a null argument is found.

I know I've avoided some common pitfalls, like forgetting to cast the null pointer in the argument list, but for whatever reason this code still isn't working. Calling the function with any number of parameters prints them properly, then fails to detect the null, prints a bunch of garbage data, and crashes.

int printline(const char *str) {
    printf("%s\n", str);
}

#define printlines(...) _comments(__VA_ARGS__, (char*)0)
int _printlines(char* first, ...) {
    if (first) {
        printline(first);

        va_list ptr;
        va_start(ptr, first);

        char *next;

        do {
            char *next = va_arg(ptr, char *);
            if (next) {
                printline(next);
            }
        } while(next);

        va_end(ptr);
    }
}

int main() {
    printlines("hi");
    //prints 'hi', then prints garbage data and crashes

    printlines("how", "are", "you");
    //prints 'how', 'are', and 'you', then prints garbage data and crashes
    
    _printlines("help", (char *)0);
    //prints 'help', then prints garbage data and crashes

    _printlines("something", "is", "wrong", (char *)NULL);
    //prints 'something', 'is', and 'wrong', then prints garbage data and crashes
}

Solution

  • If you take a look at this:

        char* next;
        do{
            char* next = va_arg(ptr,char*);
            if(next){ comment(next); }
        }while(next);
    

    You'll see that you have two separate variables called next, with the one inside of the do..while loop masking the one defined outside. You're assigning the result of va_arg to the inner next. Then when you get the while (next) condition, the inner next is out of scope and you're now reading the outer next which was never written to. This triggers undefined behavior.

    You instead want:

        char* next;
        do{
            next = va_arg(ptr,char*);
            if(next){ comment(next); }
        }while(next);
    

    So that you only have a single variable called next that you're using.