Search code examples
cgccvariadic-functionsvariadicvariadic-macros

Why does this variadic C function not print the first argument?


I am trying to make a variadic function in C with stdarg.h, and I followed this tutorial and wrote the code exactly as he did: https://www.youtube.com/watch?v=S-ak715zIIE. I included the output in the code. I cannot figure out why the first argument is not printed, and also, why are there zeros printed at the end? I am beyond confused. Thanks!

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

void printNums(int num, ...) {
    va_list args;
    va_start(args, num);

    for (int i = 0; i < num; i++) {
        int value = va_arg(args, int);
        printf("%d: %d\n", i, value);
    }

    va_end(args);
}

int main() {
    printNums(5, 2, 3, 4);
    return 0;
}

/*
Output:
0: 2
1: 3
2: 4
3: 0
4: 0
*/

Solution

  • va_start's first argument is the last parameter that isn't variadic. So num holds the 5, and the rest hold the variadics:

    #include <stdio.h>
    #include <stdarg.h>
    
    void printNums(int num, ...) {
        va_list args;
        va_start(args, num);
    
        printf("%d: %d\n", 0, num);
        for (int i = 1; i <= num; i++) {
            int value = va_arg(args, int);
            printf("%d: %d\n", i, value);
        }
    
        va_end(args);
    }
    
    int main() {
        printNums(5, 2, 3, 4);
        return 0;
    }
    
    0: 5
    1: 2
    2: 3
    3: 4
    4: 0
    5: 0
    

    also, why are there zeros printed at the end? I am beyond confused. Thanks!

    Because of this line:

        for (int i = 1; i <= num; i++) {
    

    You pass the value 5 as num to printNums(). In the for loop you act as though it describes the number of variadic arguments to read, but it doesn't - you passed 3 variadics, not 5. The last 2 calls to va_start therefore yield undefined behavior, since you've read past the end of valid variadic arguments. It's just mere chance that you happen to get 0 here - it could be some other random value.

    Note that there is no way with mere variadic macros to know how many arguments were passed. Nor is there a way to assert their type. You can assume their type and specify their length at runtime if you wish:

    $ ./t3
    0: 5
    1: 2
    2: 3
    3: 4
    
    #include <stdio.h>
    #include <stdarg.h>
    
    void printNums(int num, ...) {
        va_list args;
        va_start(args, num);
    
        for (int i = 0; i < num; i++) {
            int value = va_arg(args, int);
            printf("%d: %d\n", i, value);
        }
    
        va_end(args);
    }
    
    int main() {
        printNums(4, 5, 2, 3, 4);
        return 0;
    }
    

    Variadic functions are primarily valuable when writing functions like printf, where unknown types and quantities of arguments are required (see the example from the man page) Using passing a list of known types would be more conveniently accomplished by passing an array and count int:

    $ cat t.c
    #include <stdio.h>
    
    void printNums(int count, int* nums) {
        for (int i = 0; i < count; i++) {
            printf("%d: %d\n", i, nums[i]);
        }
    }
    
    int main() {
        int nums[] = {5,2,3,4};
        printNums(4, nums);
        return 0;
    }
    

    that just doesn't make a very good video about variadics :P