2

假设我有一个可变参数函数foo(int tmp, ...),当调用 foo 函数时,我需要知道有多少个参数。我知道有两种方法可以找出有多少参数:

  1. 调用 foo 时使用最后一个参数,例如 -1,因此您的函数调用将是这样的:foo(tmp, 1, 2, 9, -1)当您在 foo 内部并且 va_arg 调用返回 -1 时,您知道您已经阅读了所有函数参数

  2. 在 foo 中再添加一个参数,程序员将拥有参数总数,因此您将像这样调用 foo:foo(tmp, 5, 1, 2, 3, 4, 5)foo(tmp, 2, 7, 8)

我曾经遵循第一种方式,曾经有以下错误。使用代码:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e, -1)

其中 expr_of_type 是一个可变参数函数,并且正在检查 expr(第一个参数)是否是以下类型之一(boolexpr_e 或 new_table_e 或 nil_e 具有枚举类型的所有类型)。一不小心写到:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e -1)

我忘记了 nil_e 和 -1 之间的逗号,因为 nil_e 有一个枚举类型,nil_e - 1 是一个有效的表达式,并且因为 nil_e 不是 0 尝试获取 expr_of_type 参数时给定的可变参数函数没有找到 -1 作为最后一个参数和继续搜索创建了一个错误,我花了一些时间才发现。

我也觉得第二种方法不好,因为在从可变参数函数中添加或删除一个参数时,您需要更改包含总参数数量的参数。

在寻找使用/创建可变参数函数的更好方法时,我发现可变参数宏可以解决我在使用第一种方法时遇到的错误。但可变参数宏可用于 C99 标准。我一直在寻找在 C89 中使用/创建可变参数函数的更好方法。有任何想法吗?

4

3 回答 3

6

通常,您仍然必须以某种方式传递参数计数,无论是通过标记值还是通过显式计数。

但是,您可以通过制作更好的哨兵来解决哨兵问题。这就是为什么扩展为负常数的预处理器宏应该用括号括起来的原因之一:

#define VARARG_SENTINEL (-1)

然后nil_e VARARG_SENTINEL会产生编译错误。

使用enumorconst int也可以:

enum { VARARG_SENTINEL = -1 };

由于其他原因(更多的自我记录,以后更容易更改基础值),对哨兵值使用符号常量也会更好。

于 2010-07-17T17:25:48.607 回答
1

如果您编译 C99,则可以使用可变参数宏来提供可变参数,而无需显式传递计数:

#include <stdio.h>
#include <stdarg.h>
 
void _foo(size_t n, int xs[])
{
    for(int i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
}
 
#define foo(arg1, ...) do {            \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
} while(0)
 
int main()
{
    foo(1, 2, 3, 4);
    foo(-1, -2, -3, -4, -5, -6, -7);
    return 0;
}

输出:

1
2
3
4
-1
-2
-3
-4
-5
-6
-7

但是,这会阻止您返回值。您可以使用 gcc 扩展返回一个值:

#include <stdio.h>
#include <stdarg.h>
 
int _foo(size_t n, int xs[])
{
    int i;
    for(i=0 ; i < n ; i++ ) {
        int x = xs[i];
        printf("%d\n", x);
    }        
    return n;
}
 
#define foo(arg1, ...) ({              \
   int _x[] = { arg1, __VA_ARGS__ };   \
   _foo(sizeof(_x)/sizeof(_x[0]), _x); \
})
 
int main()
{
    int x = foo(1, 2, 3, 4);
    printf("foo returned %d\n", x);
    x = foo(-1, -2, -3, -4, -5, -6, -7);
    printf("foo returned %d\n", x);
    return 0;
}

输出:

1
2
3
4
foo returned 4
-1
-2
-3
-4
-5
-6
-7
foo returned 7

但是,当然,宏已经死了。宏万岁!

编辑:

哎呀,没有仔细阅读OP。对不起!

于 2010-07-17T19:02:13.450 回答
0

也总是有可能通过使用动态结构来避免完全可变参数。

struct vararray {
   uint_t n;
   uint_t params[0];
};

void foo(int tmp, struct varray *pVA);

甚至可以使用union不同大小的结构进行复杂化。

我们曾经有一个带有特定 API 的嵌入式控制器,我们在其中使用这种方法,一个union固定大小struct的传递给事件处理程序的方法。它有一些优点,因为可以使用特定的类型,并且编译器可以更好地检查函数参数的类型,因为我们不应该忘记对可变参数函数没有参数类型检查。

于 2010-07-17T18:05:01.733 回答