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:
Output two:
Output three:
Output four:
Any insight into what's happening here would be greatly appreciated!
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 */