C99 va_list 可变参数

C99 va_list 可变参数

std::va_list - cppreference.cn - C++参考手册

C99或C++11开始,头文件 stdarg.h 中声明了类型 va_list 和4个宏:va_startva_argva_endva_copy

va_list 类型存储了这4个宏进行操作所需的信息,本质是指向参数栈的一个指针/结构体。因此,再跨函数调用之间使用 va_list 时,需要保证:当一个 va_list 实例在函数 Foo 中被创建后传递给函数 Bar 并在 Bar 函数中对其使用了 va_arg 后,Foo 函数中后续再使用这个 va_list 前必须先调用 va_end

这个类型和对应的操作都是编译器内部实现的。

操作

va_start

原型:

void va_start(va_list ap, last);

功能:初始化一个 va_list 类型的变量 ap

参数:

  • ap 是一个 va_list 类型的变量,实际是一个指针,指向可变参数列表的第一个参数;
  • last 是可变参数列表 ... 之前的最后具名一个参数。因此被调用函数一定知道这个参数的类型,且这个参数不能声明为register,也不能是一个函数或数组。同时,这个参数还可以用于向函数传递一些信息,比如元素的个数,格式化字符串等。

说明:显然根据函数原型,使用 va_start 的函数如果不是直接以 va_list 作为参数,则其函数签名一定类似:

ret_type func(type1 arg1, type2 arg2, ..., typeN last_fixed_arg, ...) {
    va_list ap;
    va_start(ap, last_fixed_arg); // arg1,arg2表示可变参数前面都是固定参数。last_fixed_arg则表示要用可变参数,则必然是一个固定类型的实参作为last,然后跟着可变参数,可变参数的类型不必与last_fixed_arg的类型相同。
    // ...
    va_end(ap);
}

示例:

#include <cstdarg>
#include <iostream>
 
int add_nums(int count...)
{
    int result = 0;
    std::va_list args;
    va_start(args, count);
    for (int i = 0; i < count; ++i)
        result += va_arg(args, int);
    va_end(args);
    return result;
}
 
int main()
{
    std::cout << add_nums(4, 25, 25, 50, 50) << '\n';
}

va_arg

原型:

type va_arg(va_list ap, type);

功能:从已初始化的 va_list 变量中取出下一个参数,并返回其值。会自动将 ap 指针向后移动来指向下一个参数。

参数:ap 是一个已初始化的 va_list 变量;type 是类型名,表示期望取出的参数的数据类型,如 intdouble 等。

说明:如果实际传入的参数和 type 指定的类型不兼容,或者 ap 中已经没有更多参数了,则是未定义行为。

示例:

#include <math.h>
#include <stdarg.h>
#include <stdio.h>
 
double stddev(int count, ...)
{
    double sum = 0;
    double sum_sq = 0;
    va_list args;
    va_start(args, count);
    for (int i = 0; i < count; ++i)
    {
        double num = va_arg(args, double);
        sum += num;
        sum_sq += num*num;
    }
    va_end(args);
    return sqrt(sum_sq / count - (sum / count) * (sum / count));
}
 
int main(void)
{
    printf("%f\n", stddev(4, 25.0, 27.3, 26.9, 25.7));
}

va_end

void va_end(va_list ap);

功能:结束对可变参数列表的遍历,并执行必要的清理工作。

参数:ap 是需要清理的 va_list 变量。

说明:每一个 va_start 都必须有匹配的 va_end ,在调用完 va_end 后,ap 就是未定义的了,无法直接再次使用。

va_copy

void va_copy(va_list dest, va_list src);

功能:复制一个 va_list

参数:dest 是目标,作为 src 的副本;src 是源,被拷贝。

说明:因为 va_list 只能遍历一次,在需要多次遍历同一个可变参数列表时,可以先用 va_copy 创建一个副本。另外,使用 va_copy 初始化的 va_list ,最后也需要使用单独的 va_end 进行清理。

#include <stdarg.h>
#include <stdio.h>
#include <time.h>
 
void debug_log(const char* fmt, ...)
{
    struct timespec ts;
    timespec_get(&ts, TIME_UTC);
    char time_buf[100];
    size_t rc = strftime(time_buf, sizeof time_buf, "%D %T", gmtime(&ts.tv_sec));
    snprintf(time_buf + rc, sizeof time_buf - rc, ".%06ld UTC", ts.tv_nsec / 1000);
 
    va_list args1;
    va_start(args1, fmt);
    va_list args2;
    va_copy(args2, args1);
    char buf[1+vsnprintf(NULL, 0, fmt, args1)];
    va_end(args1); // args1 对应的清理
    vsnprintf(buf, sizeof buf, fmt, args2);
    va_end(args2); // args2 对应的清理
 
    printf("%s [debug]: %s\n", time_buf, buf);
}
 
int main(void)
{
    debug_log("Logging, %d, %d, %d", 1, 2, 3);
}
posted @ 2026-01-21 18:12  3的4次方  阅读(3)  评论(0)    收藏  举报