【问题标题】:In which cases va_list should be used在哪些情况下应该使用 va_list
【发布时间】:2011-12-08 17:23:46
【问题描述】:

我制作了一个小型 C 库,它实现了图论算法并将它们绑定以在 Python 中使用。

我把它发给朋友检查,他告诉我va_list是“危险的”,不能用于这种项目。

所以问题是。在哪些情况下应该使用va_list

【问题讨论】:

  • 如果你需要在 C 语言中接受可变数量参数的函数,你必须使用va_list——这是唯一的方法。 C 中的变量参数列表函数在某些方面是不安全的,但如果你需要它们,你就需要它们。在不了解您使用它们的目的之前,我无法就它们是否有必要提供任何建议。
  • 这是关于 C 库的。您应该删除 c++ 标记。答案将不合适。
  • 如果参数是相同类型或在具有公共基类或接口的类层次结构中,您总是可以只传递一个数组作为参数而不是使用 va_list
  • @pmr 存在 c++ 标签只是因为 va_list 也可以从 c++ 代码访问。
  • @marcushatchenson 是的,但是在 C++(尤其是 C++11)中你会得到不同的答案。

标签: c variadic-functions


【解决方案1】:

我看到的主要问题是,无法保证您确实获得了预期的参数数量,也无法检查。这使得错误无法检测,而无法检测的错误显然是最危险的类型。 va_arg 也不是类型安全的,这意味着如果您传递 double 并期望 unsigned long long,您将得到垃圾而不是漂亮的整数,并且无法在编译时检测到它. (当类型甚至没有相同的大小时,它变得更加混乱)。

根据您处理的数据,这可能或多或少是个问题。如果您传递指针,则忽略参数几乎会立即变得致命,因为您的函数将取而代之的是检索垃圾,而这可能(如果行星正确对齐)成为一个漏洞。

如果您传递“常规”数字数据,则取决于函数是否至关重要。在某些情况下,您可以通过查看函数的输出轻松检测到错误,而在某些实际情况下,如果函数失败,则问题并不大。

实际上,这一切都围绕着你是否害怕自己忘记争论。

C++11 具有可变参数模板功能,允许您以安全的方式处理任意数量的参数。如果从 C 到 C++ 的步骤并没有造成太大的伤害,您可以研究一下。

【讨论】:

    【解决方案2】:

    在 C++11 中,永远不应使用 va_list,因为它提供了更好的替代方案,称为 variadic template,它是类型安全的,而 va_list 不是。

    在 C 中,您可以在需要可变参数函数时使用 va_list,但要小心,因为它不是类型安全的。

    是的,你的朋友是对的:va_list危险。尽量避免。

    在 C 和 C++03 中,标准库函数 printf 是使用 va_list 实现的,这就是 C++03 程序员通常避免使用它的原因,因为它不是类型安全的。

    但可变参数 typesafe printf 可以在 C++11 中实现,如:(取自 wiki

    void printf(const char *s)
    {
        while (*s) {
          if (*s == '%' && *(++s) != '%')
            throw std::runtime_error("invalid format string: missing arguments");
          std::cout << *s++;
        }
    }
    
    template<typename T, typename... Args>
    void printf(const char *s, T value, Args... args)
    {
        while (*s) {
          if (*s == '%' && *(++s) != '%') {
             std::cout << value;
             ++s;
             printf(s, args...); 
             return;
          }
          std::cout << *s++;
        }
        throw std::logic_error("extra arguments provided to printf");
    }
    

    【讨论】:

    • 如果函数是 lambda 函数,你将如何使用可变参数模板?
    • @serup:我没听懂你的问题。您是在问与主题和我的回答有关的问题,还是一般来说?
    • 您说它提供了更好的替代方法,称为可变参数模板,但是想知道如何在 lambda 函数上使用可变参数模板 - 您的示例是一个普通函数,但是参数传递的用法lambda 函数有点困难,除非可以使用您所说的更好的替代方法
    • @serup: lambda 是从哪里来的?就像我问的那样,您是在谈论与该主题有关的事情吗?至于 lambda,请探索 C++14 中的可变参数 lambda。
    • 实际上这个主题并没有说明 lambda 函数的用法,所以没有和是的 - 我想知道是否可以在 lambda 函数上使用这个解决方案,或者我是否必须打开一个新问题
    【解决方案3】:

    va_list 有一些与函数参数规格不足有关的缺点:

    • 当调用这样一个函数时,编译器不知道什么类型的 参数是预期的,所以标准强加了一些“通常 在将参数传递给函数之前转换”。例如,所有 比int 窄的整数被提升,所有float 都是 晋升为double。在某些边境情况下,您没有收到您想要的 想要在被调用的函数中。
    • 在被调用函数中告诉编译器什么类型的参数 你期望和他们有多少。无法保证调用者正确。

    如果你传入参数的数量并且它们是相同的已知类型,你可以将它们传递给一个临时数组,为 C99 编写:

    void add_vertices(graph G, vertex v, size_t n, vertex neigh[n]);
    

    你可以这样称呼它

    add_vertices(G, v, nv, (vertex []){ 3, 5, 6, 7 });
    

    如果你觉得这个调用约定太难看,你可以把它包装在一个宏中

    #define ADD_VERTICES(G, V, NV, ... ) add_vertices((G), (V), (NV), (vertex [NV]){ __VA_ARG__ })
    
    ADD_VERTICES(G, v, nv, 3, 5, 6, 7);
    

    这里的... 表示宏的类似概念。但结果更安全,因为编译器可以进行类型检查,并且不会延迟执行。

    【讨论】:

    【解决方案4】:

    如果你想在 C 中实现一个带有可变参数计数的函数,你可以使用 va_list。例如,printf 使用 va_list。不知道为什么它会很危险。

    【讨论】:

    • 您是否曾经在printf 格式字符串中使用过错误的类型说明符?或者更糟糕的是,在 scanf 格式字符串中?
    • @Fed Larson,编译器制造商明白了你的意思,现在 gcc 和 clang 会针对 printf 模式的错误数据类型发出警告。不过,对于其他功能来说,这仍然是个问题。
    • @FredLarson,是的,现在我明白使用 va_list 的危险了。
    猜你喜欢
    • 2016-02-19
    • 1970-01-01
    • 1970-01-01
    • 2014-07-29
    • 1970-01-01
    • 2019-08-05
    • 2012-07-22
    • 1970-01-01
    • 2016-03-15
    相关资源
    最近更新 更多