使用 va_list 和 va_arg 实现子功能
Implementing a sub-function with va_list and va_arg
我有一个可变参数样式的函数,我想拆分成一个 va_list-style 子函数。原函数:
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);
}
请注意,调用者必须以 NULL 终止元素列表,但这不会导致任何问题。重构:
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);
}
}
然而,当我这样称呼它时:
container_append(c, NULL);
... 在 container_vappend()
内部,对 va_arg()
的调用返回了非 NULL 的内容。
这是一个更复杂的函数的转录,但除了任何拼写错误,我是否遗漏了 va_list
和 va_arg()
的设置或用法?
当container_append
这样调用时
container_append(c, NULL);
命名参数element
将为0,并且不会有任何匿名参数。在这些条件下,container_append
根本不能调用 va_arg
,否则程序有未定义的行为。在你重构代码之前它碰巧能工作,但原始代码和重构后的版本一样有问题。
您可以在循环之前检查 element
...
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);
}
... 或者您可以将所有元素参数设为匿名:
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);
}
后一种结构更符合你想做的vappend
重构。
编辑:关于评论中的这个查询:
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?
的确,这样不行。 va_start
设置 va_arg
到 return 第一个 anonymous 参数。如果没有任何匿名参数,那么您会在第一次调用 va_arg
时离开最后并触发 UB。
此外发的识别很好 ...
container_append(c, one or more arguments, NULL);
是潜在的未定义行为 (UB)。
container_append()
期望 element_t *element
而 NULL
可能只是 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
甚至没有指定与指针大小相同。
va_arg(ap, element_t *)
就是UB.
为了更安全地拨打电话,请使用container_append(c, args, (element_t *) NULL);
我有一个可变参数样式的函数,我想拆分成一个 va_list-style 子函数。原函数:
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);
}
请注意,调用者必须以 NULL 终止元素列表,但这不会导致任何问题。重构:
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);
}
}
然而,当我这样称呼它时:
container_append(c, NULL);
... 在 container_vappend()
内部,对 va_arg()
的调用返回了非 NULL 的内容。
这是一个更复杂的函数的转录,但除了任何拼写错误,我是否遗漏了 va_list
和 va_arg()
的设置或用法?
当container_append
这样调用时
container_append(c, NULL);
命名参数element
将为0,并且不会有任何匿名参数。在这些条件下,container_append
根本不能调用 va_arg
,否则程序有未定义的行为。在你重构代码之前它碰巧能工作,但原始代码和重构后的版本一样有问题。
您可以在循环之前检查 element
...
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);
}
... 或者您可以将所有元素参数设为匿名:
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);
}
后一种结构更符合你想做的vappend
重构。
编辑:关于评论中的这个查询:
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?
的确,这样不行。 va_start
设置 va_arg
到 return 第一个 anonymous 参数。如果没有任何匿名参数,那么您会在第一次调用 va_arg
时离开最后并触发 UB。
此外发的识别很好
container_append(c, one or more arguments, NULL);
是潜在的未定义行为 (UB)。
container_append()
期望 element_t *element
而 NULL
可能只是 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
甚至没有指定与指针大小相同。
va_arg(ap, element_t *)
就是UB.
为了更安全地拨打电话,请使用container_append(c, args, (element_t *) NULL);