我在很多地方看过这个,但不明白。为什么说cout比printf()更安全。仅仅因为它不需要写%d %c %f
或者它有更深层的含义。
提前致谢。
答案 0 :(得分:10)
这就是原因:
printf("%s\n", 42); // this will clobber the stream
这会导致缓冲区溢出 - 编译器通常无法检查printf
的第一个参数中的格式字符串是否与后续参数的类型相对应。 可以在上面的情况下执行此操作 - 因为字符串是硬编码的 - 而且有些编译器会这样做。 1 但通常格式字符串可以在运行时确定,所以编译器无法检查其正确性。
1 但这些检查特别适用于printf
。如果您使用与myprintf
相同的签名编写自己的printf
函数,则无法检查类型安全性,因为签名使用省略号...
删除函数内的所有类型信息。
答案 1 :(得分:7)
printf
系列函数是可变函数,因为它们都使用省略号...
,这意味着任何类型的参数( s)就...
而言可以传递给函数。编译器有无限制,因为参数的 types 上有无要求。编译器不能强加任何类型安全规则,因为省略号...
允许所有类型。该函数使用格式字符串假设参数类型(即使存在不匹配!!)。 格式字符串在运行时被读取和解释,此时编译器不能做任何事情,如果不匹配,因为代码已经编译。所以这样,这不是类型安全的。 通过类型安全,我们通常意味着编译器能够通过对(未评估的)表达式的类型强加规则来检查程序的类型一致性。
请注意,如果存在不匹配(函数无法解决!),程序将进入未定义行为区域,程序的行为无法预测,理论上任何事情都可能发生。
您可以将相同的逻辑扩展到任何可变函数函数,例如scanf
family。
答案 2 :(得分:3)
类型系统可以保证std::ostream
的正确性,而不是printf
。
Konrad的答案就是一个例子,但是像
printf("%ld\n", 7);
也被破坏(假设您的系统long
和int
大小不同)。它甚至可以与一个构建目标一起使用并且与另一个构建目尝试打印像size_t
这样的typedef也存在同样的问题。
这有点(某种程度上)通过一些编译器提供的诊断解决了,但这对第二种意义没有帮助:
两种类型系统(格式字符串中使用的运行时类型系统以及代码中使用的编译时系统)都无法自动保持同步。例如,printf
与模板交互不良:
template <typename T> void print(T t) { printf("%d\n",t); }
你无法使所有类型T
都正确 - 你可以做的最好是static_cast<int>(t)
所以如果T不能转换为int,它将无法编译。比较
template <typename T> void print(std::ostream& os, T t) { os << t << '\n'; }
为具有一个operator<<
的任何T
选择正确的{{1}}重载。
答案 3 :(得分:2)
通常,编译器不能检查printf中的参数,甚至不参数参数,也不检查它们是否适合格式化字符串。他们被“优化”以完成这项工作,但这是一个特例,可能会失败。 例如:
printf("%s %d\n", 1, "two", 3);
这将编译(除非优化的编译器检测到失败),并且在运行时,printf将第一个参数(1)视为字符串,第二个(“2”)将整数视为。 printf甚至不会注意到有第三个参数,如果没有足够的参数也不会注意到!
使用cout,编译器必须选择特定的运算符&lt;&lt;对于您插入的每个变量。 例如:
cout<<1<<"two"<<3<<endl;
编译器必须在对相应的ostream&operator<<(int)
和ostream&operator<<(const char*)
(以及ostream&operator<<(ios&(*)(ios&))
)的调用中更改此内容。
cout也会更快,因为格式字符串没有运行时解释。
答案 4 :(得分:1)
来自C++ FAQ:
[15.1]为什么我应该使用
<iostream>
而不是传统的<cstdio>
?[...]
更多类型安全:有了I / O的对象类型,编译器会静态地知道。相反,使用“%”字段动态地计算出类型。
[...]
对于printf
,编译器无法检查第一个参数的格式脚本是否与其他参数的类型相对应... 通常它是在运行时完成的。