使用变量列表参数时的va_list长度?

Ant*_*kov 34 c c++

有没有办法计算长度va_list?我看到的所有示例都明确给出了变量参数的数量.

Mot*_*tti 29

没有办法计算a的长度va_list,这就是你需要printf类似函数中的格式字符串的原因.

可用于处理a 的唯一函数va_list:

  • va_start - 开始使用 va_list
  • va_arg - 得到下一个论点
  • va_end - 停止使用 va_list
  • va_copy (自C++ 11和C99) - 复制 va_list

请注意,您需要调用va_startva_end在相同的范围内,这意味着您不能将它包装在一个实用程序类中,该实用程序类va_start在其构造函数和va_end析构函数中调用(我被这一次咬过).

例如,这个班级毫无价值:

class arg_list {
    va_list vl;
public:
    arg_list(const int& n) { va_start(vl, n); }
    ~arg_list() { va_end(vl); }
    int arg() {
        return static_cast<int>(va_arg(vl, int);
    }
};
Run Code Online (Sandbox Code Playgroud)

GCC输出以下错误

t.cpp:在构造函数中arg_list::arg_list(const int&):
第7行:错误:va_start在函数中使用,
由于-Wfatal-errors导致固定args 编译终止.

  • 嗯?不,完全允许调用`va_start()`,然后将`va_arg`传递给另一个函数,然后调用`va_end()`.这正是你使用`vsprintf()`和类似函数的方式. (4认同)
  • 另一种选择是有某种结束标记.例如,请参阅`execl()`,它会在遇到NULL时终止. (3认同)

mbo*_*t35 12

尚未提及的一种方法是使用预处理器宏来使用va_list长度作为第一个参数来调用variadict函数,并且还沿着参数转发.这有点像"可爱"的解决方案,但不需要手动输入参数列表长度.

假设您具有以下功能:

int Min(int count, ...) {
    va_list args;
    va_start(args, count);

    int min = va_arg(args, int);
    for (int i = 0; i < count-1; ++i) {
      int next = va_arg(args, int);
      min = min < next ? min : next;
    }
    va_end(args);

    return min;
}
Run Code Online (Sandbox Code Playgroud)

这个想法是你有一个预处理器宏,能够通过使用掩码来计算参数的数量__VA_ARGS__.有一些很好的预处理器库可用于确定__VA_ARGS__长度,包括P99和Boost预处理器,但我不会在这个答案中留下漏洞,这里是如何做到的:

#define IS_MSVC _MSC_VER && !__INTEL_COMPILER

/**
 * Define the macros to determine variadic argument lengths up to 20 arguments. The MSVC 
 * preprocessor handles variadic arguments a bit differently than the GNU preprocessor,
 * so we account for that here. 
 */
#if IS_MSVC
  #define MSVC_HACK(FUNC, ARGS) FUNC ARGS
  #define APPLY(FUNC, ...) MSVC_HACK(FUNC, (__VA_ARGS__))
  #define VA_LENGTH(...) APPLY(VA_LENGTH_, 0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#else
  #define VA_LENGTH(...) VA_LENGTH_(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif

/**
 * Strip the processed arguments to a length variable.
 */
#define VA_LENGTH_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N
Run Code Online (Sandbox Code Playgroud)

注意:上面的很多噪音都是对MSVC的解决方案.

通过上面定义,您可以创建一个宏来执行所有基于长度的操作:

/**
 * Use the VA_LENGTH macro to determine the length of the variadict args to
 * pass in as the first parameter, and forward along the arguments after that.
 */
#define ExecVF(Func, ...) Func(VA_LENGTH(__VA_ARGS__), __VA_ARGS__)
Run Code Online (Sandbox Code Playgroud)

只要它以int count参数开头,该宏就能调用任何variadict函数.简而言之,而不是使用:

int result = Min(5, 1, 2, 3, 4, 5);
Run Code Online (Sandbox Code Playgroud)

您可以使用:

int result = ExecVF(Min, 1, 2, 3, 4, 5);
Run Code Online (Sandbox Code Playgroud)

这是Min的模板版本,使用相同的方法:https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca

  • 非常可爱.在Min的第3行,它应该说"count"而不是"value"吗? (2认同)

Kei*_*son 10

变量函数没有直接的方法来确定传递了多少个参数.(至少没有可移植的方式; <stdarg.h>接口不提供该信息.)

有几种间接方式.

两个最常见的是:

  • 格式字符串(通过您可能称之为小型简单语言的字符串,指定剩余参数的数量和类型).该*printf()*scanf()功能的家庭使用了这种机制.
  • 表示参数结束的sentinel值.一些Unix/POSIX exec*()系列函数使用空指针标记参数的结尾.

但还有其他可能性:

  • 更简单地说,一个整数计数,指定后续参数的数量; 大概在这种情况下,他们都属于同一类型.
  • 交替参数,其中参数可以是指定以下参数类型的枚举值.一个假设的例子可能看起来像:
    func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
    甚至:
    func("-i", 42, "-s", "foo", "-d", 1.25, "");
    如果你想模仿参数通常传递给Unix命令的方式.

您甚至可以为全局变量赋值以指定参数的数量:

func_arg_count = 3;
func(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)

这将是丑陋但完全合法的.

在所有这些技术中,调用者完全有责任传递一致的参数; 被调用者只能假设其参数是正确的.

请注意,处理传递给它的所有参数不需要可变参数函数.例如,这个:

printf("%d\n", 10, 20);
Run Code Online (Sandbox Code Playgroud)

将打印10并悄然忽略20.几乎没有任何理由可以利用该功能.