printf 如何发出编译器警告?

How can printf issue a compiler warning?

我想知道函数如何发出编译时警告?

我想到了这一点,因为当我们在 printf (scanf) 的第一个参数中为与该类型说明符匹配的变量提供错误的格式说明符并使用带有 -Wall 选项的 gcc 进行编译时,编译器会发出警告。

现在,据我所知,printf 和 scanf 是定期实现的可变参数函数,我不知道有什么方法可以在编译时检查字符串的值,更不用说在不匹配时发出警告了。

谁能解释一下我是如何收到编译器警告的?

警告是实现(即编译器 & C standard library) specific. You could have a compiler giving very few warnings (look into tinycc...),甚至 none...

我正在关注最近 GCC (e.g. 4.9 or 10...) Linux。

您会收到此类警告,因为 printf 是使用适当的 __attribute__ 声明的(请参阅 GCC function attributes

(使用 GCC,您同样可以使用 format 属性声明您自己的类似 printf 的函数...)

顺便说一句,符合标准的编译器可以自由地非常特别地实现 <stdio.h> header。因此它可以在不读取任何 header 文件 的情况下处理 #include <stdio.h>,而是通过更改其内部状态。

您甚至可以添加自己的函数属性,例如通过使用 GCC plugin

自定义 GCC

How can printf issue a compiler warning?

一些编译器会在编译时分析printf()scanf()的格式和其他参数类型。

printf("%ld", 123);  // type mis-match  `long` vs. `int`
int x;
printf("%ld", &x);  // type mis-match 'long *` vs. `int *`

然而,如果计算格式,则不会进行检查,因为这是 运行 时间问题。

const char *format = foo();
printf(format, 123);  // mis-match? unknowable.

您完全正确,编译器对特定函数发出警告是不寻常的。 关于 printf(以及 scanf 和相关)格式说明符的警告 相当 不寻常——但是,这些函数首先是非常不寻常的。 正如其他答案所解释的那样,编译器至少有可能“了解”某些功能并执行像这样的特殊的、额外的编译时检查——并且假设 printfscanf 和朋友既非常不寻常又非常受欢迎,编译器进行这种额外检查是非常合适的,尽管它很不寻常。

曾几何时(我在这里谈论的是 ANSI、K&R 之前的日子),C 程序员知道他们必须小心使用正确的参数数量和类型调用函数。 (在那些日子里,自动检查的唯一方法是使用 lint,有些程序员会这样做,但很多程序员不会。)如果你习惯小心,很容易注意 printf 还有朋友们。

不过,今天情况不同了。 ANSI C 函数原型已经使用了一代人。今天的大多数程序员隐含地期望编译器自动转换函数参数的类型,并抱怨不兼容的不匹配。 (举个事态变化的例子:在过去,调用 sqrt(144) 是一个错误,悄悄地给出了神秘的结果,但今天没问题。)

所以今天,我非常同情正在学习C的程序员,被printf搞得一头雾水。如果您完全习惯了函数原型为您提供的保护,那么为什么

int i = 3;
float f = 4.5;
printf("i as a float is %f, f as an int is %d\n", i, f);

不起作用。与过去不同,我怀疑,很难记住,当你调用 printf 时(但几乎只有当你调用 printf 时),它是 你的 正确获取所有类型的工作,因为编译器不会插入任何隐式转换。

底线是,今天,编译器不仅可以警告 printf 等调用中的不匹配,我相信这几乎是道德上的当务之急。当我们引入函数原型时,我们向程序员承诺了函数参数的类型安全性,因此当涉及到 printf.

时悄悄撤回该承诺确实不公平

[P.S。是的,我当然知道 为什么 函数原型不能保证像 printf 这样的可变参数函数的完全类型安全。但这与我在这里的论点无关。另外,是的,我知道,生活是不公平的,所以用我高谈阔论的“道德律令”称我为老软蛋。 :-)]