是否需要在具有多个“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()”?的主要内容,如果未能解决你的问题,请参考以下文章

python详解python函数定义 def()与参数args可变参数*args关键参数**args使用实例

JavaScript——函数定义和调用

java函数概述

python之函数

js 函数

具有多个返回参数的 C# 方法[重复]