使用指向const char的指针作为va_start的第二个参数

时间:2015-02-27 15:27:30

标签: c unix pointers

我正在UNIX环境3版中进行高级编程,我找到了这段代码

err_msg(const char *fmt, ...)
{
    va_list     ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
} 

err_doit是:

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char    buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout);     /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL);       /* flushes all stdio output streams */
}

我不理解的是作者为const char传递指向va_start的指针的原因。据我所知,你只允许传递省略号部分所代表的参数数量,如下所述:

void f1(int n, ...);
int f2(const char * s, int k, ...);

最右边的参数(省略号之前的参数)起着特殊的作用;标准使用 术语parmN作为讨论中使用的名称。在前面的示例中,parmN将为n 对于第一种情况,k用于第二种情况。 传递给此参数的实际参数将是 是省略号部分表示的参数数量。 源: Stephen Prita的C Primer Plus。

我还检查了用 ISO C标准编写的两个示例:7.15变量参数,我发现他们也将 int 变量传递给{{ 1}}宏。

恐怕是我在某个地方错过了一点。我希望有人告诉我。

4 个答案:

答案 0 :(得分:5)

va_start的第二个参数是var-args参数开始之前的最后一个“真实”参数。

err_msg()函数只有一个普通参数const char *fmt指针,所以很明显这是var-args部分之前的最后一个参数,应该传递给va_start()

The manual page说:

  

参数last是变量参数列表之前的最后一个参数的名称,也就是调用函数知道类型的最后一个参数。

va_list的第二个参数的实际类型并不重要。

另请注意,相当常见的printf()函数很可能就是这样,因为它也只有一个“普通”参数,即格式字符串。

答案 1 :(得分:3)

来自Wikipedia

  

然后使用两个参数调用宏va_start:第一个是声明类型va_list的变量,第二个是函数的最后一个命名参数的名称。

或者来自C standard library

  

C库宏void va_start(va_list ap, last_arg)初始化ap变量以与va_argva_end宏一起使用。 last_arg是传递给函数的最后一个已知固定参数,即。省略号之前的参数。

关键是您需要...之前的标准参数才能将其提供给va_startva_start将仅使用它来检索补充参数(纯C烹饪)。

最后一个标准参数不相关,但可能想要用它来知道补充参数的数量。这也是一个使用示例,我认为这是您的来源所做的:一个使用示例。

答案 2 :(得分:3)

假设您已正确引用该书,该声明:

  

传递给此参数的实际参数将是省略号部分表示的参数数量。

不正确,或至少具有误导性。

在您的具体示例中:

void f1(int n, ...);
int f2(const char * s, int k, ...);

省略号恰好是int之前的最后一个命名参数,其值可能用于指定与...对应的参数数量。

这是一个合理的约定(尽管你仍然需要指定那些参数的 types ),但语言不需要它。

va_start()的第二个参数始终是最后一个命名参数的名称(不是值)。由于va_start是宏,而不是函数,因此通过值传递所有参数的通常规则不适用。在内部,va_start使用参数名称来确定内存(或寄存器)中可以找到其他参数值的位置。 (没有可移植的方法来执行此操作,因此标准未指定va_start的实际定义;必须为每个编译器自定义它。)

所有真正需要的是该函数必须能够以某种方式找出 调用者传递的参数的数量和类型。 printf通过解析格式字符串来完成此操作。 execl()(POSIX函数,未由ISO C定义)通过假设所有参数都是char*类型并收集参数直到找到空指针来完成它。其他机制也是可能的。如果调用者传递不一致的参数,则行为未定义;调用者无法检测到错误。

即使在您的示例中,va_start的第二个参数是而不是“省略号部分表示的参数数量”。这是参数的名称。即使参数值恰好是3,将文字3传递给va_start也是不正确的。该函数将通过读取命名参数的值而不是与va_start相关的任何内容来确定参数的数量。

va_start中定义了<stdarg.h>宏的语义以及{{1}}中定义的其他项目。

答案 3 :(得分:0)

C标准只要求省略号之前的最后一个参数是默认提升的,而不是用函数或数组类型声明(尽管允许使用函数指针和指针类型,所以这只是一个语法限制)。

对于f1f2,最后一个命名参数包含传递的参数数量,尽管这完全是传统的。

标准库中以不同方式处理的示例是printf(参数的数量(和类型)由格式字符串确定)和execl(参数列表的末尾是用NULL指针标记。