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

C printf和printf(“s”)之间的根本区别是什么?,c,string,printf,compiler-warnings,format-specifiers,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 c

问题很简单,
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)
之间的根本区别是什么?为什么我只在一个案例中得到警告?

警告说明了一切

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

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


也就是说,关于警告选项,
-Wformat security

目前,这会警告调用
printf
scanf
函数时,格式字符串不是字符串文字,并且没有格式参数,如
printf(foo)。如果格式字符串来自不受信任的输入并包含
%n
,则这可能是一个安全漏洞

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

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


更新:

关于第三个,提供的格式字符串需要额外的参数

printf(s, 99, 50);
引用自
C11
,第§7.21.6.1章

[…]如果在参数保留时格式已用尽,则多余的参数将被删除 已评估(一如既往),但在其他方面被忽略。[……]


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

在第一种情况下,非文字格式字符串可能来自用户代码或用户提供的(运行时)数据,在这种情况下,它可能包含未传递数据的
%s
或其他转换规范。这可能会导致各种各样的阅读问题(如果字符串包含
%n
-请参阅或您的C库手册页,则会导致书写问题)

在第二种情况下,格式字符串控制输出,无论要打印的任何字符串是否包含转换规范(尽管显示的代码打印的是整数,而不是字符串)。编译器(问题中使用的是GCC或Clang)假设,由于(非文字)格式字符串后面有参数,程序员知道它们在干什么

第一个是“格式字符串”漏洞。您可以搜索有关该主题的更多信息

GCC知道,大多数情况下,带有非文字格式字符串的单个参数
printf()
会带来麻烦。您可以使用
put()
fputs()
。GCC以最少的挑衅发出警告是非常危险的

如果您不小心,非文字格式字符串的更一般问题也会有问题,但如果您小心的话,这是非常有用的。您必须更加努力地让GCC投诉:它需要
-Wformat
-Wformat nonliteral
来投诉

从评论中:

因此,忽略警告,好像我真的知道我在做什么,并且不会有错误,是一个或另一个更有效地使用,还是它们是相同的?同时考虑空间和时间

在三条
printf()
语句中,考虑到变量
s
是在调用上方分配的,因此没有实际问题。但是,如果从字符串中省略换行符或
fputs(s,stdout)
,则可以使用
put(s)
,并获得相同的结果,而无需对整个字符串进行
printf()
解析,以找出所有要打印的简单字符

第二个
printf()
语句也是安全的;格式字符串与传递的数据匹配。这与简单地将格式字符串作为文本传递没有显著区别,只是编译器可以做更多的检查,如果格式字符串是文本。运行时结果是相同的

第三个
printf()。但这并不理想。同样,编译器可以更好地检查格式字符串是否为文本,但运行时效果实际上是相同的

从顶部链接到的
printf()
规范:

每个函数都在格式的控制下转换、格式化和打印其参数。格式为字符串,以其初始移位状态(如果有)开头和结尾。该格式由零个或多个指令组成:普通字符(仅复制到输出流)和转换规范,每个转换规范将导致获取零个或多个参数。结果是
const char* s = "abcdefghij\n";
printf(s);
printf("abcdefghij\n");
int printf(const char *fmt, ...) __attribute__ ((format(printf, 1, 2)));