printf(s)和printf(“%s”,s)之间的根本区别是什么?

时间:2016-09-09 15:59:48

标签: c string printf compiler-warnings format-specifiers

问题很简单,s是一个字符串,我突然想到尝试使用printf(s)来查看它是否有用,我在一个案例中收到警告而没有在另一个。

char* s = "abcdefghij\n";
printf(s);

// Warning raised with gcc -std=c11: 
// format not a string literal and no format arguments [-Wformat-security]

// On the other hand, if I use 

char* s = "abc %d efg\n";
printf(s, 99);

// I get no warning whatsoever, why is that?

// Update, I've tested this:
char* s = "random %d string\n";
printf(s, 99, 50);

// Results: no warning, output "random 99 string".

那么printf(s)printf("%s", s)之间的根本区别是什么?为什么我只会在一个案例中收到警告?

5 个答案:

答案 0 :(得分:25)

在第一种情况下,非文字格式字符串可能来自用户代码或用户提供的(运行时)数据,在这种情况下,它可能包含%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。)

答案 1 :(得分:6)

警告说明了一切。

首先,要讨论问题,根据签名,printf()的第一个参数是格式字符串,可以包含格式说明符(< em>转换说明符)。如果 string 包含格式说明符且未提供相应的参数,则会调用undefined behavior

因此,更清晰或更安全)方法(打印不需要格式规范的字符串)将puts(s);超过printf(s);前者不会为任何转换说明符处理s,在后一种情况下删除可能的UB的原因)。如果您担心fputs()中自动添加的结尾换行符,您可以选择puts()

也就是说,关于警告选项,-Wformat-security from the online gcc manual

  

目前,这会警告调用printfscanf函数,其中格式字符串不是字符串文字,并且没有格式参数,如{{ 1}}。如果格式字符串来自不受信任的输入并包含printf (foo);,则可能是安全漏洞。

在你的第一种情况下,只有一个参数提供给%n,它不是字符串文字,而是变量,它可以在运行时很好地生成/填充,如果它包含意外的格式说明符,它可能会调用UB。编译器无法检查其中是否存在任何格式说明符。那是那里的安全问题。

在第二种情况下,提供了附带的参数,格式说明符不是传递给printf()唯一参数,因此第一个参数不需要验证。因此警告不存在。

更新

关于第三个,使用提供的格式字符串

所需的多余参数
printf()

引自printf(s, 99, 50); ,章节§7.21.6.1

  

[...]如果参数保留时格式已用尽,则多余的参数为   评估(一如既往),否则被忽略。 [...]

因此,传递多余的参数根本不是问题(从编译器的角度来看)并且定义良好。没有任何警告的范围。

答案 2 :(得分:5)

你的问题有两个方面。

第一个被Jonathan Leffler简洁地覆盖 - 你得到的警告是因为字符串不是文字的,并且没有任何格式说明符。

另一个是为什么编译器不会发出警告,说明您的参数数量与说明符数量不匹配的原因。简短的回答是“因为它没有”,但更具体地说,printf是一种可变函数。它在初始格式规范之后需要任意数量的参数 - 从0开始。编译器无法检查您是否给出了正确的数量;这取决于printf函数本身,并导致Joachim在评论中提到的未定义行为。

修改: 我将进一步回答你的问题,作为上一个小肥皂盒的一种方法。

printf(s)printf("%s", s)之间有什么区别?简单 - 在后者中,您使用的是printf,因为它已声明。 "%s" const char *,随后它不会生成警告消息。

在您对其他答案的评论中,您提到“忽略警告......”。不要这样做。警告存在是有原因的,应该得到解决(否则它们只是噪音,而且你会错过所有那些没有警告的警告。)

您的问题可以通过多种方式解决。

const char* s = "abcdefghij\n";
printf(s);

将解决警告,因为你现在正在使用一个const指针,并且Jonathan没有提到任何危险。 (您也可以将其声明为const char* const s,但不必如此。第一个const很重要,因为它与printf的声明匹配,因为const char* s表示s指向的字符不能更改,即字符串是文字。)

或者,更简单,只需:

printf("abcdefghij\n");

这是一个const指针,也不是问题。

答案 3 :(得分:2)

根本原因:printf被声明为:

int printf(const char *fmt, ...) __attribute__ ((format(printf, 1, 2)));

这告诉gcc printf是一个带有printf样式接口的函数,其中格式字符串首先出现。恕我直言,它必须是字面的;我不认为有一种方法可以告诉好的编译器s实际上是一个它以前见过的文字字符串的指针。

详细了解__attribute__ here

答案 4 :(得分:2)

  

那么printf(s)和printf(&#34;%s&#34;,s)之间的根本区别是什么

&#34; printf的(一个或多个)&#34;将s视为格式字符串。如果s包含格式说明符,那么printf将解释它们并继续查找varargs。由于实际上不存在varargs,因此可能会触发未定义的行为。

如果攻击者控制了&#34; s&#34;那么这可能是一个安全漏洞。

printf(&#34;%s&#34;,s)将只打印字符串中的内容。

  

为什么我只在一个案例中收到警告?

警告是在捕捉危险的愚蠢与不产生太多噪音之间取得平衡。

C程序员正处于使用printf和各种类似printf的函数*作为通用打印功能的习惯,即使他们实际上并不需要格式化。在这种环境下,如果有人在不考虑s来源的情况下编写printf(s)的错误,就很容易。由于没有任何数据来格式化printf(s)几乎没有合法用途,格式化是相当无用的。

另一方面,

printf(s,format,arguments)表示程序员故意打算进行格式化。

默认情况下,在上游gcc中没有打开此警告,但是有些发行版正在将其打开,以减少安全漏洞。

*标准C函数如sprintf和fprintf以及第三方库中的函数。