【问题标题】:Use variadic functions in C89 without passing number of arguments or a final argument?在 C89 中使用可变参数函数而不传递参数数量或最终参数?
【发布时间】:2010-07-17 17:04:21
【问题描述】:

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

    1234563函数参数
  1. 在 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 为最后一个论点并继续搜索创建了一个错误,我花了一些时间才发现。

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

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

【问题讨论】:

    标签: c enums c89 variadic-functions


    【解决方案1】:

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

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

    #define VARARG_SENTINEL (-1)
    

    那么nil_e VARARG_SENTINEL会产生编译错误。

    使用enumconst int 也可以:

    enum { VARARG_SENTINEL = -1 };
    

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

    【讨论】:

    • 忘记逗号时使用enumconst int会导致同样的问题吗?
    • 不,不会。如果你有const int VARARG_SENTILEL = -1;,当你忘记逗号时,你会得到类似nil_e VARARG_SENTINEL的东西,它会给出一个编译错误。当您使用enum { VARARG_SENTINEL = -1 }; 时也会发生同样的事情。在忘记逗号时使用这两种解决方案,就像拥有number1 number2(例如2 3),这是一个无效的表达式,但是当像我以前那样使用-1时,你将拥有number - 1,这是一个有效的表达式。
    【解决方案2】:

    如果您编译 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。对不起!

    【讨论】:

      【解决方案3】:

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

      struct vararray {
         uint_t n;
         uint_t params[0];
      };
      
      void foo(int tmp, struct varray *pVA);
      

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-10-20
        • 1970-01-01
        • 1970-01-01
        • 2015-09-22
        • 2017-07-05
        • 2020-12-27
        • 1970-01-01
        相关资源
        最近更新 更多