是否需要在具有多个“return”语句的可变参数函数中的每个“return”语句之前调用“va_end()”?
Posted
技术标签:
【中文标题】是否需要在具有多个“return”语句的可变参数函数中的每个“return”语句之前调用“va_end()”?【英文标题】:Does `va_end()` need to be invoked before each `return` statement in a variadic function with multiple `return` statements? 【发布时间】:2021-09-03 19:57:51 【问题描述】:关于va_end()
,有一件事总是让我感到困惑。我经常读到这不是一个实际的函数,而是一个预处理器宏。虽然这听起来像是一个无关紧要的细节,但它实际上可能会影响需要调用 va_end()
的位置。
问题很简单:在具有多个return
语句的可变参数函数中,是否需要在每个return
语句之前调用va_end()
?
以下示例中的可变参数函数具有返回其参数的第一个非NULL
的简单任务。如您所见,函数体中有三个return
语句。其中之一出现在va_start()
之后但在va_end()
之前。
这段代码正确吗?
#include <stdio.h>
#include <stdarg.h>
static const void * const FIRST_NON_NULL_END = (void *) "";
void * first_non_null (
const void * const ptr1,
...
)
if (ptr1)
return ptr1 == FIRST_NON_NULL_END ? NULL : (void *) ptr1;
void * retval;
va_list args;
va_start(args, ptr1);
do
retval = va_arg(args, void *);
if (retval == FIRST_NON_NULL_END)
/* Is this correct? Here I do not invoke `va_end()`! */
return NULL;
while (!retval);
va_end(args);
return retval;
int main ()
const char * const my_string = first_non_null(
NULL,
NULL,
"autumn",
NULL,
"rose",
FIRST_NON_NULL_END
);
printf("The first non-null value is: \"%s\".\n", my_string);
return 0;
编辑:
为了澄清一下,上面的示例以这种形式编写仅用于教学目的。在现实生活中,它可以用一种更好、更综合的方式重写为:
#include <stdio.h>
#include <stdarg.h>
static const void * const FIRST_NON_NULL_END = (void *) "";
void * first_non_null (
const void * const ptr1,
...
)
const void * retval;
va_list args;
va_start(args, ptr1);
for (retval = ptr1; !retval; retval = va_arg(args, const void *))
;;
va_end(args);
return retval == FIRST_NON_NULL_END ? NULL : (void *) retval;
int main ()
const char * const my_string = first_non_null(
NULL,
NULL,
"autumn",
NULL,
"rose",
FIRST_NON_NULL_END
);
printf("The first non-null value is: \"%s\".\n", my_string);
return 0;
【问题讨论】:
C11 7.16.1.3: "...如果在返回之前没有调用va_end
宏,则行为未定义。"
“invoked before”的通常含义是关于执行顺序。所以你把它当作一个需要调用的函数。您的代码有 UB。
换一种说法:它应该总是在你的函数的生命周期内被调用一次,恰好在你的函数的生命周期内(即在它的启动之后并且在它的return
之前)。
从技术上讲,C 标准允许 va_end
是一个诚实的函数而不是宏 (C17 7.16.1p1),在这种情况下,预期的解释似乎很明显。
谢谢大家的回答。
【参考方案1】:
来自 C 标准(7.16.1 变量参数列表访问宏)
1 ... va_start 和 va_copy 宏的每次调用都应匹配 通过在相同的 va_end 宏的相应调用 功能。
所以如果va_start
在任何return语句之前使用并且va_end
还没有被调用,那么它应该被调用。
【讨论】:
您好弗拉德,谢谢您的回答。虽然你的解释并不能完全说服我。可以将句子“va_start 和 va_copy 宏的每次调用都应与同一函数中 va_end 宏的相应调用相匹配”翻译为:va_start()
出现一次,因此va_end()
在函数内只能出现一次,独立于return
语句的数量。
@madmurphy 如果控件被传递给 return 语句并且前面的代码包含对 va_start 的调用,那么您将调用 va_end。否则 va_start 后面不会跟着 va_end。
如果va_end()
是一个预处理器宏,它将始终被访问,预处理器对return
语句一无所知。这就是我问的原因。
@madmurphy 宏会被展开,但并不意味着它的代码会被执行。例如,您可以编写一个插入带有函数调用的表达式的宏。但这并不意味着表达式会被求值,因为控件可以绕过表达式所在的语句。
@madmurphy 任何所谓的“技巧”都是代码。以上是关于是否需要在具有多个“return”语句的可变参数函数中的每个“return”语句之前调用“va_end()”?的主要内容,如果未能解决你的问题,请参考以下文章