我有一个我想将其拆分为va_list式子功能的varargs式函数。原始功能:
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()
的呼叫正在返回并非零的东西。
这是一个更复杂的功能的转录,但是除非任何错别字,否则我是否错过了va_list
和va_arg()
的设置或使用中的某些内容?
除了由@zwol识别出的良好确定...
container_append(c, one or more arguments, NULL);
是电势未定义的行为(ub(。
container_append()
期望element_t *element
和NULL
可能只是0
。
NULL
扩展为实现定义的空指针常数...具有值0的整数常数表达式,或这样的表达式铸型
void *
,称为 null指针常数。
NULL
甚至没有指定与指针相同的大小。
va_arg(ap, element_t *)
是ub。
要进行更安全的电话,请使用container_append(c, args, (element_t *) NULL);
当 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
以返回匿名参数的第一个。如果没有任何匿名论点,那么您第一次致电va_arg
并触发UB。