如何判断 va_list 是不是为空

Posted

技术标签:

【中文标题】如何判断 va_list 是不是为空【英文标题】:How to determine if va_list is empty如何判断 va_list 是否为空 【发布时间】:2012-07-25 15:22:47 【问题描述】:

我一直在阅读一些编译器支持带有宏的 va_list 并且用户能够overload the functionality with other macros in order to count the va_list。

使用 Visual Studio,有没有办法确定 va_list 是否为空(又名 count==0)?基本上我想知道这种情况:

extern void Foo(const char* psz, ...);
void Test()

  Foo("My String"); // No params were passed

我最初的想法是做这样的事情:

va_list vaStart;
va_list vaEnd;
va_start(vaStart, psz);
va_end(vaEnd);
if (vaStart == vaEnd) ...

问题在于 va_end 仅将参数设置为空。

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)      ( ap = (va_list)0 )

我在考虑可能合并一个terminator,但我希望它对调用者隐藏,这样就不需要更改现有代码。

【问题讨论】:

【参考方案1】:

没有办法知道通过... 传递了多少参数,也不知道它们是什么类型。仅当您有其他方式(例如printf-style 格式字符串)告诉函数期望什么时,才能使用可变函数参数;即便如此,也无法验证这些论点。

C++11 提供类型安全的可变参数模板。我不知道您的编译器是否支持这些,或者它们是否适合您的问题。

【讨论】:

这将导致对重载函数的模糊调用:( @BabelFish:确实如此。我原以为会选择非可变版本,但似乎我错了。 谢谢。我假设“没有办法告诉”也适用于 Objective-C……我不是假设你会知道,但也许。谢谢!【参考方案2】:

我意识到这个问题已经相当老了,但我认为稍微充实一下可能会有所帮助。正如 Mike Seymour 非常正确地回答的那样,没有绝对可靠的方法来确定 va_list 中的参数数量。这就是为什么定义可变参数函数的常规方法是包含一个包含该信息的参数,例如:void func(const char *str, int count, ...);,它定义了调用者应该遵守的合同。

编辑:标准(7.16.1.1.3)实际上对va_arg(vl, type)返回的值保持沉默,对于任何超过变量参数列表末尾的调用。大多数类型最常见的情况是类型为零。但是,CAVEAT EMPTOR - 不必如此。

当没有更多参数时从va_arg(vl, type) 返回的值是一个类型为零的值。对于数字类型,它是 0。对于指针,它是一个 NULL 指针。对于结构,它是一个所有字段都归零的结构。如果您选择复制va_list 并尝试计算副本,如下所示:

void func(const char *str, ...) 
    va_list vl;
    va_list vc;
    int x;
    int count;

    count = 0;
    va_start(vl, str);
    va_copy(vc, vl);
    do 
        x = va_arg(vc, int);
        if (x == 0) break;
        count++;
     while (1)
    va_end(vc);
    .
    .
    . // do something, or something else,
    . // based on the number of args in the list
    .
    va_end(vl);

您必须假设调用者会遵守合同,不会在列表中传递 NULL 或零值。无论哪种方式,您都必须了解可变参数函数的调用者有责任遵守规定的合同。所以写你的函数,发布合约,然后睡个好觉。

【讨论】:

您在哪里找到此信息?根据 cppreference:“如果在 ap 中没有更多参数时调用 va_arg,则行为未定义。”另外,我尝试使用 gcc6,但没有得到输入为零的值。 如何处理可变参数在很大程度上取决于特定平台的 ABI。直接的基于堆栈的方法绝对无法确定参数的数量;它只是走上堆栈,阅读那里发生的任何事情。除了(如前所述)您的实际参数可能包含零这一事实之外,没有理由相信您的参数之后的堆栈垃圾是零。参考 C99 第 7.15.1.1 节的权威“行为未定义”。【参考方案3】:

我知道我的答案不是“正统”答案,但是由于 va_list 宏的限制,我发现以下解决方案可行。我们可以将 va_list 视为一个字符数组,甚至可以将其视为一个以 null 结尾的字符数组,因此您可以尝试

va_list argptr;
if(strcmp(argptr,"")) 

   // not empty

else

   // empty

我试过了,它对我有用。 我使用了 Visual Studio 2013 Ultimate。项目类型为 Win32 控制台应用程序。没有编译错误。

【讨论】:

你应该编辑你的答案,说明它工作的编译器(包括版本)。我的 GCC 7.2 拒绝此代码。 我花时间构建了一个测试程序。我使用了 Visual Studio 2013 Ultimate。项目类型为 Win32 控制台应用程序。没有编译错误。 这行不通。 va_list 类型的底层类型通常选择为char*,因为char* 可以为任何其他指针类型起别名。这不会使指向的内存成为字符数组。考虑void foo(int, ...); foo(0, 0);,并尝试您提出的解决方案。对strcmp 的调用将表现出未定义的行为,并且可能会报告一个空列表(尽管不是)。有时你更幸运,反而触发了访问冲突。

以上是关于如何判断 va_list 是不是为空的主要内容,如果未能解决你的问题,请参考以下文章

如何将 va_list 传递给 wsprintfW()?

如何使用 va_lists 独立格式化 C 字符串平台?

我应该如何将NULL传递给va_list函数参数?

动态创建 va_list

memset() 对 va_list 的含义是啥?

[C]va_list可变长参数的使用