为什么printf只有一个参数(没有转换说明符)不推荐使用?
在我正在阅读的一本书中,写了带有一个参数(不带转换说明符)的printf被弃用。 它build议替代 
 printf("Hello World!"); 
同
 puts("Hello World!"); 
要么
 printf("%s", "Hello World!"); 
 有人可以告诉我为什么printf("Hello World!"); 是错的? 它是写在书中,它包含漏洞。 这些漏洞是什么? 
 printf("Hello World!"); 恕我直言,不容易,但考虑到这一点: 
 const char *str; ... printf(str); 
 如果str恰好指向包含%s格式说明符的string,程序将显示未定义的行为(大部分是崩溃),而puts(str)将仅显示string。 
例:
 printf("%s"); //undefined behaviour (mostly crash) puts("%s"); // displays "%s" 
 printf("Hello world"); 
是好的,没有安全漏洞。
问题在于:
 printf(p); 
 其中p是指向由用户控制的input的指针。 它很容易格式化string攻击 :用户可以插入转换规范来控制程序,例如%x转储内存或%n覆盖内存。 
 请注意, puts("Hello world")在行为上与printf("Hello world")而是printf("Hello world\n") 。 编译器通常足够聪明,可以优化后者的调用来replaceputs 。 
 除了其他的答案之外, printf("Hello world! I am 50% happy today")是一个很容易造成的bug,可能会导致各种令人讨厌的内存问题(这是UB!)。 
只要程序员想要一个逐字串而没有其他任何东西时 ,它就更简单,更简单,更强大。
 这就是printf("%s", "Hello world! I am 50% happy today") 。 这完全是万无一失的。 
  (Steve,当然是printf("He has %d cherries\n", ncherries)绝对不是一回事,在这种情况下,程序员并不是“逐字串”的心态,而是“格式化”的心态。 ) 
我将在这里添加一些有关漏洞部分的信息。
据说由于printfstring格式的漏洞,它是脆弱的。 在你的例子中,string是硬编码的,这是无害的(即使硬编码这样的string永远不会被完全推荐)。 但是指定参数的types是一个很好的习惯。 以这个例子:
如果有人将格式string字符放在printf中,而不是常规string(例如,如果要打印程序标准input),printf将采取任何他可以在堆栈上进行的操作。
现在(现在仍然)非常习惯于利用程序来探索堆栈来访问隐藏的信息或绕过authentication。
例(C):
 int main(int argc, char *argv[]) { printf(argv[argc - 1]); // takes the first argument if it exists } 
 如果我把这个程序input为"%08x %08x %08x %08x %08x\n" 
 printf ("%08x %08x %08x %08x %08x\n"); 
这指示printf函数从堆栈中检索五个参数,并将其显示为8位填充的hex数字。 所以可能的输出可能如下所示:
 40012980 080628c4 bffff7a4 00000005 08059c04 
看到这个更完整的解释和其他例子。
这是错误的build议。 是的,如果您有打印的运行时string,
 printf(str); 
是相当危险的,你应该总是使用
 printf("%s", str); 
 相反,因为通常你永远不会知道str是否可能包含%符号。 但是,如果你有一个编译时常量string,没有任何问题 
 printf("Hello, world!\n"); 
(除此之外,这是C程序中最经典的C程序,字面意思是来自“创世纪”的C编程书籍,所以任何贬低这个用法的人都是相当邪教的,而我一个人会有点冒犯他人!
 使用文字格式string调用printf是安全和有效的,并且存在一些工具可以在用户提供的格式string对printf的调用不安全时自动发出警告。 
 对printf最严重的攻击利用%n格式说明符。 与所有其他格式说明符(例如%d相反, %n实际上是将一个值写入其中一个格式参数中提供的内存地址。 这意味着攻击者可以覆盖内存,从而有可能控制你的程序。  维基百科提供了更多细节。 
 如果用string格式string调用printf ,攻击者就不能将%n隐藏到格式string中,因此您是安全的。 实际上,gcc会把你的调用改为printf ,所以在这里没有任何区别(通过运行gcc -O3 -Stesting)。 
 如果使用用户提供的格式string调用printf ,攻击者可能潜入%n到您的格式string中,并控制您的程序。 你的编译器通常会警告你他的不安全,请参阅-Wformat-security 。 还有一些更高级的工具可以确保printf的调用在用户提供的格式string中是安全的,他们甚至可能会检查是否将正确数量和types的parameter passing给printf 。 例如,对于Java,有Google的错误倾向和检查器框架 。 
  printf一个相当讨厌的方面是,即使在杂散内存读取的平台上只能造成有限(和可接受)的伤害的平台之一,格式化字符%n的一个导致下一个参数被解释为指向可写整数的指针,并且使得到此为止输出的字符的数量被存储到由此识别的variables中。 我从来没有使用过这个function,有时候我使用了轻量级的printf风格的方法,我只写了一些我实际使用的function(不包括那一个或者类似的东西),但是提供了标准的printf函数string从不可靠的来源可能暴露的安全漏洞超出了读取任意存储的能力。 
由于没有人提到,我会添加一个关于他们的performance的笔记。
 在正常情况下,假设没有使用编译器优化(即printf()实际调用printf()而不是fputs() ),我期望printf()执行效率较低,特别是对于长string。 这是因为printf()必须parsingstring来检查是否有任何转换说明符。 
为了证实这一点,我已经运行了一些testing。 testing在Ubuntu 14.04上执行,使用gcc 4.8.4。 我的机器使用Intel i5 cpu。 正在testing的程序如下:
 #include <stdio.h> int main() { int count = 10000000; while(count--) { // either printf("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"); // or fputs("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", stdout); } fflush(stdout); return 0; } 
 两者都是用gcc -Wall -O0编译的。 时间是使用time ./a.out > /dev/null来测量的。 以下是典型运行的结果(我运行了五次,所有结果都在0.002秒之内)。 
 对于printf()变体: 
 real 0m0.416s user 0m0.384s sys 0m0.033s 
 对于fputs()变体: 
 real 0m0.297s user 0m0.265s sys 0m0.032s 
如果你有很长的string,这个效果会被放大。
 #include <stdio.h> #define STR "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" #define STR2 STR STR #define STR4 STR2 STR2 #define STR8 STR4 STR4 #define STR16 STR8 STR8 #define STR32 STR16 STR16 #define STR64 STR32 STR32 #define STR128 STR64 STR64 #define STR256 STR128 STR128 #define STR512 STR256 STR256 #define STR1024 STR512 STR512 int main() { int count = 10000000; while(count--) { // either printf(STR1024); // or fputs(STR1024, stdout); } fflush(stdout); return 0; } 
 对于printf()变体(运行三次,实际加/减1.5s): 
 real 0m39.259s user 0m34.445s sys 0m4.839s 
 对于fputs()variables(运行三次,实际加/减0.2s): 
 real 0m12.726s user 0m8.152s sys 0m4.581s 
  注意:在检查gcc生成的程序集之后,我意识到gcc优化了对fwrite()调用的fputs() fwrite()调用,即使使用-O0 。  ( printf()调用保持不变。)我不确定这是否会使我的testing失效,因为编译器会在编译时计算fwrite()的string长度。 
 printf("Hello World\n") 
自动编译
 puts("Hello World") 
你可以通过diassembling你的可执行文件来检查它:
 push rbp mov rbp,rsp mov edi,str.Helloworld! call dword imp.puts mov eax,0x0 pop rbp ret 
运用
 char *variable; ... printf(variable) 
会导致安全问题, 千万不要用printf这种方式!
所以你的书实际上是正确的,使用printf与一个variables已弃用,但你仍然可以使用printf(“我的string\ n”),因为它会自动成为投入
 对于gcc,可以启用特定的警告来检查printf()和scanf() 。 
gcc文档指出:
-Wformat包含在-Wall。 为了更好地控制格式检查的某些方面,选项-Wformat-y2k-Wno-format-extra-args,-Wno-format-extra-args,-Wno-format-zero-length,-Wformat-nonliteral,-Wformat-security和-Wformat=2可用,但不包含在-Wall。
 在-Wall选项中启用的-Wformat不会启用几个有助于查找这些情况的特殊警告: 
-   -Wformat-nonliteral会发出警告,如果你不传递一个string作为格式说明符。
-   -Wformat-security会警告你传递一个可能包含危险结构的string。 它是-Wformat-nonliteral的一个子集。
 我不得不承认,启用-Wformat-security揭示了我们在代码库中的一些错误(日志模块,error handling模块,xml输出模块,都有一些函数可以做未定义的事情,如果在参数中用%字符调用的话对于信息,我们的代码库现在已经有20年了,即使我们意识到了这些问题,当我们启用这些警告时,仍然有多less这些错误仍在代码库中,我们感到非常惊讶。