Search code examples
cvariadic-functions

C: variadic functions with void pointers


I'm new to C and trying to create a variadic function that takes in void pointers. However, I'm getting some unexpected behavior that I don't really understand...

token.h

typedef struct {
    int (*funcOne) (void*, ...);
    ...
} symbols_t;

main.c

typedef struct {
    char* name;
} job_t;

int job_create(void* objPtr, ...) {
    char* name;
    int i = 0;

    va_list args;
    va_start(args, objPtr);

    while(&objPtr[i]) {
        void* arg = va_arg(args, void*);
        if (arg == NULL) {
            break;
        }

        printf("arg %u: %s\n\n", i, arg);
        i++;
    }
    va_end(args);

    return EXIT_SUCCESS;
}

int main(void) {
    symbols_t symbolTable;
    symbolTable.funcOne = *job_create;

    job_t myJob;

    // See output one
    symbolTable.funcOne(&myJob, "hey1", "hey2", "hey3", "hey4", "hey5", "hey6", "hey7", "hey8);

    // See output two
    symbolTable.funcOne(&myJob, "hey1", "hey2");

    // See output three
    symbolTable.funcOne(&myJob, "hey1");

    // See output four
    symbolTable.funcOne(&myJob);

    return EXIT_SUCCESS;
}

Output one:

enter image description here

Output two:

enter image description here

Output three:

enter image description here

Output four:

enter image description here

Any insight into what's happening here would be greatly appreciated!


Solution

  • Your code looks for va_arg to return NULL in order to exit the loop, and that would only happen if you actually explicitly pass NULL as an argument. The compiler doesn't magically add it. As such, after running through the arguments actually passed, the loop continues reading arguments that aren't there, which is undefined behavior (a likely outcome is that you read whatever garbage is in the unused registers or memory).

    So your call should look more like

    /* still not quite right, see below */
    symbolTable.funcOne(&myJob, "hey1", "hey2", NULL);
    symbolTable.funcOne(&myJob, NULL); /* pass no strings */
    

    However, a couple points to make your program more correct: a string literal has type char *, not void *, so there is a mismatch between the type actually passed and the type you request from va_arg. That causes undefined behavior, in general. Your job_create function should instead be doing char * arg = va_arg(args, char*);.

    Then, passing NULL as the last argument is not quite right. The macro NULL is allowed to be defined as either (void *)0 or simply 0. That leads to the same mismatch in either case: your va_arg is now expecting char *, but what's being passed is either void * or int. The latter case is particularly dangerous on machines where int is smaller than a pointer.

    You need to pass a null pointer of type char *. So the correct way to call this function would be:

    symbolTable.funcOne(&myJob, "hey1", "hey2", (char *)NULL);
    symbolTable.funcOne(&myJob, (char *)NULL); /* pass no strings */