我有一个varargs样式的函数,我想拆分成va_list样式的子函数。原始功能:
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()
时是否错过了某些东西?
答案 0 :(得分:5)
除了已被@zwol识别出的问题...
container_append(c, one or more arguments, NULL);
是潜在的不确定行为(UB)。
container_append()
期望element_t *element
和NULL
可能只是0
。
甚至没有指定
NULL
扩展为实现定义的空指针常量...值为0的整数常量表达式,或将其强制转换为类型的表达式
void *
,称为空指针常量。
NULL
与指针的大小相同。
va_arg(ap, element_t *)
是UB。
要拨打更安全的电话,请使用container_append(c, args, (element_t *) NULL);
答案 1 :(得分:4)
这样叫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
重构更加兼容。
编辑:关于此查询的评论:
我认为
va_start(ap, element)
(在调用方中)将设置va_arg以首先返回元素。也许那样行不通?
的确,它不能那样工作。 va_start
设置va_arg
返回第一个 anonymous 参数。如果没有任何匿名参数,那么您第一次调用va_arg
时就结束了,并触发了UB。