6

我在很多地方读过这个,但不明白。为什么说 cout 比 printf() 更安全。只是因为它不需要写%d %c %f或者它有一些更深的含义。

提前致谢。

4

5 回答 5

10

这就是为什么:

printf("%s\n", 42); // this will clobber the stream

这将导致缓冲区溢出——编译器通常无法检查第一个参数中的格式字符串是否printf对应于后续参数的类型。在上述情况下它可以做到这一点——因为字符串是硬编码的——一些编译器会这样做。1但一般而言,格式字符串可能在运行时确定,因此编译器无法检查其正确性。


1但这些检查对printf. myprintf如果您使用与 相同的签名编写自己的函数printf,则无法检查类型安全性,因为签名使用省略号...,省略了函数内的所有类型信息。

于 2013-07-22T13:57:32.233 回答
7

printf系列函数是可变参数函数,因为它们都使用省略号...,这意味着任何类型的参数都可以传递给函数...。编译器没有限制,因为对参数类型没有要求。编译器不能强加任何类型安全规则,因为省略号允许所有类型。该函数使用格式字符串来假定参数类型(即使存在不匹配!!)。格式字符串在运行时被读取和解释,此时编译器无法...如果由于代码已经编译而出现不匹配,请执行任何操作。所以通过这种方式,这不是类型安全的。通过类型安全,我们通常意味着编译器能够通过对(未评估的)表达式的类型施加规则来检查程序的类型一致性。

请注意,如果存在不匹配(函数无法确定!),程序将进入未定义行为区域,在该区域中程序的行为是不可预测的,理论上任何事情都可能发生。

您可以将相同的逻辑扩展到任何可变参数函数,例如scanffamily。

于 2013-07-22T14:00:41.597 回答
3
  1. std::ostream类型系统用但不保证正确性printf。康拉德的回答就是一个例子,但类似

    printf("%ld\n", 7);
    

    也坏了(假设您的系统大小不同)longint它甚至可能与一个构建目标一起工作而与另一个构建目标一起失败。尝试像打印 typedef 一样size_t有同样的问题。

    这(在某种程度上)通过一些编译器提供的诊断来解决,但这对第二种意义没有帮助:

  2. 两种类型系统(格式字符串中使用的运行时类型系统和代码中使用的编译时系统)都不能自动保持同步。例如,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有一个的重载选择正确的重载。

于 2013-07-22T14:05:07.950 回答
2

一般来说,编译器不能检查 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 也会更快,因为没有格式字符串的运行时解释。

于 2013-07-22T15:15:52.217 回答
1

C++ 常见问题解答

[15.1] 为什么要使用<iostream> 而不是传统的<cstdio>

[...]

更类型安全:使用 ,被 I/O 的对象类型由编译器静态知道。相反,使用“%”字段来动态计算类型。

[...]

对于printf,编译器无法检查第一个参数的格式脚本是否对应于其他参数的类型......通常它是在运行时完成的。

于 2013-07-22T14:00:01.263 回答