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