【问题标题】:How can we apply a non-vararg function over a va_list?我们如何在 va_list 上应用非 vararg 函数?
【发布时间】:2011-11-23 07:14:53
【问题描述】:

背景故事

我正在将QuickCheck 单元测试框架移植到C(请参阅GitHub 的工作代码)。语法将是:

for_all(property, gen1, gen2, gen3 ...);

其中property 是要测试的函数,例如bool is_odd(int)gen1gen2 等是为property 生成输入值的函数。有些生成整数,有些生成字符,有些生成字符串,等等。

for_all 将接受具有任意输入(任意数量的参数,任意类型的参数)的函数。 for_all 将运行生成器,创建测试值以传递给属性函数。例如,属性is_odd 是一个类型为bool f(int) 的函数。 for_all 将使用生成创建 100 个测试用例。如果属性为其中任何一个返回 false,for_all 将打印有问题的测试用例值。否则,for_all 将打印 "SUCCESS"

因此for_all 应该使用va_list 来访问生成器。一旦我们调用了生成器函数,我们如何将它们传递给属性函数?

示例

如果is_odd 具有bool f(int) 类型,我们将如何实现具有以下语法的函数apply()

apply(is_odd, generated_values);

次要问题

SO

我们如何智能地打印失败测试用例的任意值?一个测试用例可能是一个整数,或者两个字符,或者一个字符串,或者以上的某种组合?我们不会提前知道是否使用:

  • printf("%d %d %d\n", some_int, some_int, some_int);
  • printf("%c\n" a_character);
  • printf("%s%s\n", a_string, a_struct_requiring_its_own_printf_function);

【问题讨论】:

  • 我不认为我遵循 - 你应该 a) 调用每个生成器一次并立即接收整个生成值集合还是 b) 在循环中调用每个生成器函数以获得后续值要测试?
  • 好的,我已经阅读了源代码,所以我知道它实际上是c) 每个生成器为特定的函数参数提供后续值。
  • 首先:运行给定的生成器来创建一个测试用例。示例:testme(int, char, char) 需要一个随机整数和两个随机字符。一旦我们弄清楚如何做到这一点,我们将让for_all 在一个大循环中运行不止一个,而是 100 个测试用例。
  • 对于您的次要问题,您需要生成 args 并将它们布置在您也传递的特殊日志记录函数中,或者通过启动 va_list 然后传递 started 列出某种打印或处理函数(您可以使用 va_list 类型作为函数的参数,允许您使用 vararg 函数进行包装和其他处理)。
  • @peachykeen 是的,我想我必须将 API 更改为 for_all(property, gen1, print1, gen2, print2, ...); 才能处理任意复杂的数据类型(想想 RedBlack 树)。

标签: c unit-testing tuples quickcheck


【解决方案1】:

C 语言是一种静态类型语言。它不具备其他语言所具备的运行时反射功能。它也不提供从运行时提供的类型构建任意函数调用的方法。您需要有一些方法知道is_odd 的函数签名是什么,它接受多少个参数以及这些参数的类型是什么。它甚至不知道它何时到达 ... 参数列表的末尾;你需要一个明确的终止符。

enum function_signature {
    returns_bool_accepts_int,
    returns_bool_accepts_float,
    returns_bool_accepts_int_int,
};

typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_generates_int)();

void for_all(function_signature signature, ...)
{
    va_list ap;
    va_start(ap, signature);
    switch (function_signature)
    {
    case returns_bool_accepts_int:
        {
            function_returning_bool_accepting_int fn = va_arg(ap, function_returning_bool_accepting_int);
            function_generates_int generator;
            do {
                generator = va_arg(ap, function_generates_int);
                if (generator) fn(generator());
            } while (generator);
        }
        break;
    ... etc ...
    }
}

您的问题是 QuickCheck 旨在利用 JavaScript 的高动态可编程性,而 C 中缺少一些东西。

更新如果您允许任意函数签名,那么您需要一种方法使其再次成为静态,例如让调用者提供适当的适配器。

typedef void (*function_pointer)();
typedef bool (*function_applicator)(function_pointer, function_pointer);

void for_all(function_applicator apply, ...)
{
    va_list ap;
    va_start(ap, apply);
    function_pointer target = va_arg(ap, function_pointer);
    function_pointer generator;
    do {
        generator = va_arg(ap, function_pointer);
        if (generator) apply(target, generator);
    } while (generator);
}

// sample caller
typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_returning_int)();
bool apply_one_int(function_pointer target_, function_pointer generator_)
{
    function_returning_bool_accepting_int target = (function_returning_bool_accepting_int)target_;
    function_returning_int generator = (function_returning_int)generator_;
    return target(generator());
}

for_all(apply_one_int, is_odd, generated_values1, generated_values2, (function_pointer)0);



}

【讨论】:

  • 是的,C 不能动态确定函数的类型签名。这就是必须提供显式生成器函数的原因。而且我们不能对 enum 进行硬编码,因为用户可能希望通过自定义类型(结构、联合等)测试函数。
  • 然后你必须将输入推送给调用者。见更新。 (填补我作为练习留下的空白。我不能为你做所有事情......)
  • 嘿,不,我不希望你这样做。 :) 在您的代码中,似乎 is_odd 分别在 generate_values1 和 generated_values2 上被调用。请记住,每个生成器都代表属性输入的一部分。 is_odd(int) 的元数为 1,因此它应该有一个生成器 (gen_int)。 is_equal_to(int a, int b) 的数量为 2,因此它将有两个生成器 (gen_int, gen_int)。
  • 欢迎您扩展设计以支持arity大于1的功能。
  • 该设计需要为每个可能的函数签名创建辅助函数。我会将无限组合的枚举留给其他人练习。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多