当我们在没有格式说明符的情况下为printf()
提供多个参数时,它的行为是什么?
示例:
int main()
{
printf("hello", "hi");
return 0;
}
为什么编译器在编译上述程序时会发出警告?:
warning: too many arguments for format [-Wformat-extra-args]
如果我们编译下面的类似程序:
int main()
{
char *s1 = "hello";
char *s2 = "hi";
printf(s1, s2);
}
没有产生任何警告。这是什么原因?
另外,为什么两个程序都只输出hello
,而不打印hi
?
C 2018标准在第7.21.6.3条中规定了printf
的行为,其中第2段说:"printf
函数等效于fprintf
,参数stdout
插在printf
的参数之前。">
该标准在7.21.6.1中规定了fprintf
的行为,这告诉我们第二个参数(printf
的第一个参数)是一个格式字符串,它可能包含字符"%"引入的各种转换规范。因此,在printf("hello", "hi")
中,"hello"
是没有转换规范的格式字符串。在这种情况下,第2段告诉我们发生了什么:
如果格式已用尽[已完全处理],而参数仍保留,则会计算多余的参数(一如既往),但在其他情况下会被忽略。
因此,在printf("hello", "hi")
中,"hi"
被忽略,而"hello"
是一个仅包含普通字符的格式字符串,这些字符将根据第3段复制到输出流中。
编译器对printf("hello", "hi")
发出警告,因为它能够看到此调用包含多余的参数,因为格式字符串不包含其转换规范。
编译器不会对printf(s1,s2);
发出警告,因为它不会分析s1
在此调用期间将包含的内容。在这种情况下,这种分析并非不可能,但像这样的情况很少见:当程序员使用指向字符串的指针作为printf
的格式字符串时,通常是在程序执行过程中计算、构造或选择的字符串或指针,而这种计算方式往往超出了编译器的分析能力。指针显然是指向固定字符串的指针的情况很少见,因为它们通常并不有用,所以编译器实现者可能认为实现编译器处理这些情况所需的代码没有价值。
tl;dr:忽略printf()
的额外参数
官方的C语言标准(链接到C11版本的草案)说:
§7.21.6.1
fprintf
函数
。。。
。。。如果在保留参数的情况下耗尽了格式,则会计算多余的参数(一如既往),但在其他情况下会忽略这些参数。当遇到格式字符串的末尾时,
fprintf
函数返回。
。。。而CCD_ 23只是以标准输出文件为目标的CCD_。
关于您的两个代码片段:
-
对于第一个代码段,编译器会提示您,参数的数量与格式字符串中的说明符数量不匹配。这只是一种礼貌——不需要注意到这一点。这也解释了为什么编译器在第二个代码段中没有注意到它。它可以,但要追踪你的指针并检查它们指向什么太费力了。
-
在这两种情况下,您的格式字符串都是
printf()
的第一个参数,即"hello"
。该字符串没有格式说明符,因此printf()
查看"hello"
,并理解它只需要打印它,而不需要处理任何其他参数。这就是它忽略"hi"
的原因。
printf
的第一个参数是格式字符串,因为printf
是关于打印格式化的数据。为了指定如何格式化数据,printf
使用第一个参数。这与其他语言和库不同,在这些语言和库中,所有参数(如Python的print
)都以相同的方式使用,并且通过其他方式进行格式化。
您提供的第一个和第二个例子都是";不正确的";尽管在技术上是有效的,因为您传递的格式字符串不需要任何额外的参数,所以"hi"
是未使用的。
你可能想做的是:
printf("%s %s", "hello", "hi");
许多编译器非常熟悉printf
函数族,并在编译时读取分析参数的格式字符串。printf("hello",s2);
编译器发现格式字符串中没有%...
,并且不需要任何其他参数。发出警告
如果您调用printf(s1,s2);
,编译器不知道s1
的内容是什么,并且它无法通过格式字符串,并且不会发出警告。
许多编译器都有特殊的扩展,以通知它们您的函数类似于printf
,并且您希望编译器读取格式字符串-gcc:
extern int
my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));