【问题标题】:How to determine the end of va_arg list?如何确定 va_arg 列表的结尾?
【发布时间】:2023-03-16 00:54:01
【问题描述】:

我有一个函数foo(char *n, ...); 我需要获取并使用所有可选的char 参数。 我有一个想法使用

while(va_arg(argPtr, char) != NULL)
{
   ...
}

了解我何时到达列表末尾。那么,如果在函数调用中我会做foo(n, 't', 'm', '$', NULL);,它会起作用吗?

NULL 会被 va_arg 读取为字符吗? 或者也许有一种更常见的方法来确定列表的结尾,而不添加 NULL 作为最后一个参数?

【问题讨论】:

  • 如果n 是一个格式字符串,那么您通常只需将格式说明符的数量与参数的数量相匹配,就可以像printf 和家族一样行事。你确定它只是一个可变数量的chars 被传递吗?如果是这样,您可以匹配 -10 或其他东西,但由于@hvd 在下面的 cmets 中概述的原因,NULL 不是一个好主意。
  • @kekyc 如果您依赖负的 char 值,那么您应该使用 signed char,如果您只处理 [0, 128) 范围 (ascii) 中的值,那么 - 1 应该没问题。
  • 如果你要传递一个可变长度的char 参数列表,为什么不只传递一个字符串呢? (有一些种情况下这不起作用,但这是显而易见的解决方案。)
  • 如果你有可变数量的相同类型的参数,由一个特殊的值终止,那么这个值需要匹配参数的类型。如果您要传递int 值,那么您需要选择一些特殊的int 值,例如0 或-1。如果您要传递字符串,即char *,则需要使用(char *) NULL 终止它,例如execl() 需要。
  • 好的,谢谢你们,给了很棒的完整答案!

标签: c variadic-functions


【解决方案1】:

对于使用va_arg 来确定给定调用传递的参数的数量或类型的函数没有直接方法。

特别是您提出的方法:

while(va_arg(argPtr, char) != NULL)

不正确。 va_arg(argPtr, char) 产生一个char 类型的值,而NULL 是一个空指针常量。 (NULL 通常定义为0,比较等于空字符'\0',但你不能依赖它。)

任何可变参数函数都必须有方法让调用者指定参数的数量和类型。例如,*printf 函数通过(非可变)格式字符串执行此操作。 POSIX execl*() 函数需要一系列 char* 参数;调用者用(char*)NULL 标记参数列表的结尾。其他方法是可能的,但它们几乎都依赖于在运行时在参数中给出的信息。 (您可以使用其他方法,例如全局变量。请不要。)

这给调用者带来了确保传递给函数的参数一致的负担。函数本身无法确认这一点。不正确的调用,如printf("%d\n", "hello")execlp("name", "arg1") 具有未定义的行为

还有一件事:您不能将va_argchar 类型的参数一起使用。当您调用可变参数函数时,对应于, ... 的参数被提升。类型小于int 的整数参数被提升为intunsigned intfloat 类型的参数被提升为double。如果调用者传递char 类型的参数,则函数必须调用va_arg(argPtr, int)

(在您不太可能遇到的非常模糊的情况下,char 可以提升为unsigned int。只有当普通的char 是无符号的并且sizeof (int) == 1 时才会发生这种情况,这意味着一个字节至少是 16 位。)

【讨论】:

  • 非常漂亮和完整。晦涩的方法在参数中包括一个全局变量或 something(不一定是结束标记)。然而,这个很好的答案提到的两种方式是最常见的。
  • @chux:关于全局变量的要点。我添加了适当的黄鼠狼字。
  • 我记得在 foo(...) 被允许的日子里这些弱编码实践 - 在 C89 之前。
  • @chux:很有趣。我不知道 pre-ANSI 可变参数函数(大概使用<varargs.h> 来获取它们的参数)使用... 语法。
  • @Edenia:在 C 中,NULL 扩展为“实现定义的空指针常量”。空指针常量被定义为“值为 0 的整数常量表达式,或这种类型转换为 typevoid * 的表达式”。所以NULL 可以扩展到((void*)0)0('-'-'-') 中的任何一个,如果实施者心情不好的话。 int n = NULL; 是否会产生诊断取决于实现。 (注意 C++ 有不同的规则。)
【解决方案2】:

基本的想法是可行的。但是您填写的细节几乎肯定不会。

当您使用可变参数时,通常的隐式转换规则不适用。相反,默认参数提升发生,这意味着任何小于 int/unsigned int 的整数类型都会转换为其中之一——这不是唯一的提升,而是唯一相关的-- 这也意味着不会自动转换为您使用va_arg 指定的任何类型。

这意味着:

  • 您永远不能使用va_arg(..., char),因为不可能将char 作为可变函数参数传递。
  • 您永远不能将NULL 作为可变参数函数参数传递,因为它的具体类型在很大程度上依赖于实现。现实的类型是intlongvoid *,还有很多其他不太现实但同样有效的类型。

你可以改变

while(va_arg(argPtr, char) != NULL)

while(va_arg(argPtr, int) != 0)

和电话

foo(n, 't', 'm', '$', NULL);

foo(n, 't', 'm', '$', 0);

【讨论】:

    【解决方案3】:

    在这一点上只是晚了几年,但我有一些有趣的贡献。在这种情况下,我会使用预处理器。请注意使用可变参数的错误类型。

    注意:给函数名加上括号会使宏忽略它同时允许文件符号与 API 不同(宏使用除外)。

    方法一:哨兵宏

    这样屏蔽你的函数:

    #define my_func(...) my_func(__VA_ARGS__, NULL);
    void (my_func)(...)
    {
       /* ... */
    }
    

    方法二:数组长度宏

    将可变参数转换为int[],以静态确定sizeof 表示的数组:

    #define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))              
    #define NARGS(...) ARRAY_LENGTH((int[])({ __VA_ARGS__ }))
    

    类似的掩码,但在前面添加一个计数参数:

    #define my_func(...) my_func(NARGS(__VA_ARGS__), __VA_ARGS__);
    void (my_func)(int n, ...)
    {
       /* ... */
    }
    

    数组宏的编译特定类型检查:

    超出了问题的范围,但因为我在roll

    我总是使用 GCC,有时也会使用以下内容。我相信 Clang 中有半兼容的功能,但我不确定。这使您可以确定传递的是实际数组而不是指针。

    #define TYPE_COMPATABLE(x, y) __builtin_types_compatible_p(__typeof__(x), __typeof__(y))
    #define CHOOSE_EXPR(b, x, y) __builtin_choose_expr((b), (x), (y))
    #define IS_ARRAY(a) (!TYPE_COMPATABLE((a), &(a)[0]))
    #define ARRAY_LENGTH(a)                           \
        ({                                            \
            _Static_assert(IS_ARRAY(a), "non-array"); \
            sizeof(a) / sizeof((a)[0]);               \
        })
    
    #define NARGS(...) ARRAY_LENGTH((int[])({ __VA_ARGS__ }))
    

    到那时,可能只会使用 C++ 呵呵。

    【讨论】:

      猜你喜欢
      • 2019-02-01
      • 1970-01-01
      • 2015-01-31
      • 2019-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-04
      • 1970-01-01
      相关资源
      最近更新 更多