C

C语言编写可变参数函数

问题汇总

Posted by iStar on March 19, 2021

本文是自己在使用<stdarg.h>头文件时遇到的一些问题和总结

stdarg.h 介绍

<stdarg.h>是用于在 C 语言中实现可变参数的函数的头文件,可变参数的函数是在参数列表的末尾使用省略号(…)定义的。它具有三个函数和一个变量:

  • 变量:va_list,是存储所有输入参数的列表。
  • 函数:
    • void va_start(va_list ap, last_arg):用于初始化 va_list 型的 ap 变量,last_arg 是最后一个非可变参数,即它之后应该跟着”…“;
    • type va_arg(va_list ap, type):用于从 va_list 中读取参数,type 为传入参数的类型;
    • void va_end(va_list ap):在参数列表读取结束后,用于结束这个过程。

stdarg.h 的使用

实例

在多叉树的实现中,下面的函数用于一次加入多个子节点:

void AddChildren(Node* parent, int childnum, ...)
{
    if (parent == NULL) return;
    va_list child_list;
    va_start(child_list, childnum);
    for (int i = 0; i < childnum; i++) {
        parent->children[parent->child_ptr++] = va_arg(child_list, Node*);
    }
    va_end(child_list);
}

问题案例

还有一种错误的实现,这种实现将在打印树结构时报错Segmentation fault (core dumped)

void AddChildren(Node* parent, ...)
{
    if (parent == NULL) return;
    Node* child = NULL;
    va_list child_list;
    va_start(child_list, parent);
    while (child = va_arg(child_list, Node*)) {
        parent->children[parent->child_ptr++] = child;
    }
    va_end(child_list);
    child = NULL;
}

错误分析

我从 StackOverFlow 上找到相关问题和解答:stdarg.h reads too many arguments 简单总结如下: 在使用 va_arg()时,需要明确知道传入的参数个数,否则 va_arg()将会一直读取内存,直到不满足 while()条件,这就导致有时会从栈中读取额外的内容。

It is your responsibility to communicate to your function how many arguments there are and what their types are.

上面的介绍可以得出第一种解决方案,就是本文中提到的实例,通过向函数传入一个额外的参数来实现。

此外还有另一种解决方案,就是利用传入的参数手动终止 va_arg()的读取。例如上面的错误案例,在使用时可以通过再最后额外加一个NULL来得到正确结果,即AddChildren(parent, child1, child2, NULL);这样就可以在读取完子节点后终止while()循环。