为什么printf需要强制参数?

时间:2015-05-14 19:02:51

标签: c printf

C语言中printf函数的定义是:

int printf(const char * _Format, ...);

同样适用于scanf和许多类似的函数,其中管理了可变数量的参数。

为什么有_Format必填参数?

5 个答案:

答案 0 :(得分:7)

格式字符串是必需的,因为C&#39的可变参数宏的工作方式取决于至少存在一个参数,并使用它来查找其他参数。

具体来说,要读取其他变量参数,请使用va_start(然后重复va_arg,对于要读取的每个变量参数,使用一次。当你调用va_start时,你需要传递格式字符串(或者更常见的是,函数的最后一个非变化参数)。

例如,这就像printf一样,但会打印到stdout和您选择的另一个文件:

void tee(FILE *f, char const *fmt, ...) { 
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
    va_start(ap, fmt);
    vfprintf(f, fmt, ap);
    va_end(ap);
}

这使用vprintfvfprintf,因此它不会(直接)使用va_arg本身,只有va_startva_end,但是&{1}} #39;足以显示fmt如何使用va_start

有一段时间,实际上并不需要这样做。当C闪亮而且新的时候,你可以拥有一个等同于int f(...);的函数。

然而,在第一次C标准化工作期间,这被取消,有利于上面提到的需要至少一个命名参数的宏(va_startva_argva_end)。较旧的宏对调用约定提出了许多要求:

  1. 无论类型或编号如何,参数始终以相同的方式传递。
  2. 总是很容易找到传递的第一个参数。
  3. 使用传统的C调用约定(所有参数都在栈上传递,参数从右向左推送)这是真的。你基本上只是查看堆栈的顶部,向后移动到返回地址,并且有第一个参数。

    使用其他调用约定,事情并非如此简单。例如,只需从左向右推送参数意味着第一个参数(格式字符串,在printf的情况下)被埋在堆栈中的任意距离,在其后面有任意数量的其他参数。 / p>

    他们提出处理这个问题的方法是将前一个(命名)参数传递给va_start(而va_start是一个通常使用该参数地址的宏)。如果你从右向左推,那将给你一个地址,无论距离堆栈需要什么距离,然后va_arg可以向后走堆栈以检索其他变量参数。

    这显然被认为是一种可接受的折衷方案,特别是因为采用变量参数的函数几乎总是至少采用一个命名参数。

答案 1 :(得分:4)

因为它不想猜猜要打印什么

答案 2 :(得分:1)

这是强制性的,因为printf用于来打印数据。想象一下,如果你什么都不打印就会发生什么。 没有。那么,为什么要删除该参数?

关于scanf的问题是一样的:你需要以某种方式读取数据如果你不知道格式的话,你将如何做?这个数据?

某些功能没有参数,因为他们不需要参数,例如

void Hello(void) { puts("Hello"); }

因此,他们可以在没有参数的情况下生存' 。关于printf

int printf(void) { //imaginary function, don't use it!
    // WTF? What to print?
    // Absolutely nothing! What's the purpose then?
    return smth;
}

然后当没有传递参数时,此printf 绝对无用。

答案 3 :(得分:1)

通常,具有未知数量参数的函数依赖于va_startva_argva_end来处理未在函数参数列表中显式的参数。

va_start需要使用最后一个命名参数。因此,具有未知数量的参数的函数必须至少具有一个命名参数。

对于printf,指定格式规范的参数/参数是作为必需参数/参数的最佳选择。

答案 4 :(得分:0)

如果没有格式说明,printf将无法理解要打印的内容。对C来说,一切都只是字节,所以printf不知道传递给它的是什么类型的数据,因此不知道如何表示它。

当你刚接触C时,你可能还没有意识到这是多么真实,特别是如果你已经学会了一种print()理解它所看到的数据类型的语言。