Reputation: 35159
I have a varargs-style function that I want to split out to a va_list-style sub-function. The original function:
void container_append(container_t *c, element_t *element, ...) {
element_t *e;
va_list ap;
va_start(ap, element);
while((e = va_arg(ap, element_t *)) != NULL) {
container_append_aux(c, e);
}
va_end(ap);
}
Note that the caller must terminate the list of elements with a NULL, but that should not cause any problems. Refactored:
void container_append(container_t *c, element_t *element, ...) {
va_list ap;
va_start(ap, element);
container_vappend(c, ap);
va_end(ap);
}
void container_vappend(container_t *c, va_list ap) {
element_t *e;
while ((e = va_arg(ap, element_t *)) != NULL) {
container_append_aux(c, e);
}
}
However, when I call it like this:
container_append(c, NULL);
... inside of container_vappend()
, the call to va_arg()
is returning something that isn't NULL.
This is a transcription of a more complicated function, but barring any typos, did I miss something in the setup or usage of va_list
and va_arg()
?
Upvotes: 1
Views: 509
Reputation: 153338
In addition to issued identified well by @zwol ...
container_append(c, one or more arguments, NULL);
is potential undefined behavior (UB).
container_append()
expects a element_t *element
and NULL
may be simply 0
.
NULL
which expands to an implementation-defined null pointer constant ...An integer constant expression with the value 0, or such an expression cast to type
void *
, is called a null pointer constant.
NULL
is not specified even to be the same size as a pointer.
va_arg(ap, element_t *)
is then UB.
To make a safer call, use container_append(c, args, (element_t *) NULL);
Upvotes: 5
Reputation: 140495
When container_append
is called like this
container_append(c, NULL);
the named parameter element
will be 0, and there won't be any anonymous parameters. Under those conditions, container_append
must not call va_arg
at all, or the program has undefined behavior. It happened to work by accident before you refactored the code, but the original code is just as buggy as the refactored version.
You can either check element
before the loop...
void
container_append(container_t *c, element_t *element, ...)
{
if (!element) return;
container_append_aux(c, element);
va_list ap;
va_start(ap, element);
while ((element = va_arg(ap, element_t *)))
container_append_aux(c, element);
va_end(ap);
}
... or you can make all of the element arguments be anonymous:
void
container_append(container_t *c, ...)
{
va_list ap;
va_start(ap, c);
element_t *e;
while ((e = va_arg(ap, element_t *)))
container_append_aux(c, e);
va_end(ap);
}
The latter structure is more compatible with the vappend
refactor you want to do.
EDIT: Regarding this query in a comment:
I thought
va_start(ap, element)
(in the caller) would set up va_arg to return element first. Maybe it doesn't work that way?
Indeed, it does not work that way. va_start
sets up va_arg
to return the first of the anonymous arguments. If there weren't any anonymous arguments, then you walk off the end the very first time you call va_arg
, and trigger UB.
Upvotes: 4