在第一种情况下,非字面量格式化字符串可能来自用户代码或用户提供的(运行时)数据,这种情况下它可能包含“%s”或其他转换规范,而您没有传递数据。这可能会导致各种阅读问题(如果字符串包括“%n”,还可能导致写作问题——请参见
printf()
或者您的C库手册页)。
在第二种情况下,格式化字符串控制输出,无论要打印的任何字符串是否包含转换规范都没有关系(尽管所示的代码打印的是一个整数,而不是字符串)。编译器(问题中使用的是GCC或Clang)认为,由于在(非字面量)格式化字符串之后有参数,程序员知道他们要做什么。
第一种情况是“格式化字符串”漏洞。您可以搜索更多关于此主题的信息。
GCC知道大多数情况下带有非字面量格式化字符串的单个参数
printf()
很容易出问题。您可以改用
puts()
或
fputs()
。它太危险了,以至于GCC生成警告的最低限度的挑衅。
非文字格式化字符串的更一般的问题也可能会有问题,如果您不小心的话——但是假设您小心使用,它也非常有用。要让GCC发出抱怨,您必须更加努力:它需要同时使用
-Wformat
和
-Wformat-nonliteral
才能发出抱怨。
从评论中可以看到:
引用:
“因此,忽略警告,就好像我真的知道自己在做什么并且不会出错,一种方法比另一种更有效还是它们相同?考虑空间和时间。”在三个`printf()`语句中,由于变量`s`在调用之前被赋值并被密切使用,因此没有实际问题。但是如果您省略了字符串中的换行符,则可以使用`puts(s)`,或者保持不变并使用`fputs(s, stdout)`,从而获得相同的结果,而无需通过`printf()`解析整个字符串以查找要打印的所有简单字符的开销。
第二个`printf()`语句所写的也是安全的;格式字符串与传递的数据匹配。直接传递格式字符串和传递它的字面量之间没有显着区别 - 除非编译器可以对字面量进行更多检查。运行时结果是相同的。
第三个`printf()`将比格式字符串需要的参数数量更多的数据参数传递,但这是良性的。虽然编译器可以更好地检查字面量格式字符串,但运行时效果几乎相同。
来自上述链接的`printf()`规范:
每个函数都在控制格式下转换、格式化和打印其参数。格式是一个字符串,在其初始换档状态(如果有)下开始和结束。格式由零个或多个指令组成:普通字符,只需简单地复制到输出流中,以及转换说明符,每个说明符应导致获取零个或多个参数。如果格式缺少参数,则结果是未定义的。如果格式在参数仍然存在的情况下耗尽,则将评估超额参数,但否则将被忽略。
在所有这些情况下,没有明显的迹象表明为什么格式字符串不是字面值。然而,想要一个非字面值的格式字符串的原因之一可能是有时您需要以%f
表示法打印浮点数,有时需要以%e
表示法打印,并且您需要在运行时选择其中一种方式。(如果只基于值,%g
可能是适当的,但有时您需要显式控制-始终使用%e
或始终使用%f
)。
const char* const s
,您可能会注意到一些差异。 - ascheplerprintf(s)
容易出现错误(并且可能存在安全漏洞),而printf("%s", s)
只是一种低效的编写方式,等价于fputs(s, stdout)
。 - Ilmari Karonen