如何证明cout比printf()更加类型安全?

7
我在许多地方都看到过这个说法,但是不理解。为什么说cout比printf()更加类型安全?只是因为它不需要写%d %c %f或者有更深层的含义吗?
提前致谢。

2
因为我们明确地传递了格式字符串,所以如果我将“%d”拼写错误为“%f”,那么就会出现未定义的行为。 - Grijesh Chauhan
2
可能是printf vs cout in C++的重复问题。 - 0decimal0
既然你提到类型安全,请参考https://dev59.com/Q2445IYBdhLWcg3wmLdd和https://dev59.com/DnI-5IYBdhLWcg3wF0Uc等链接。 - devnull
1
printf是一个可变参数函数https://en.wikipedia.org/wiki/Variadic_function,它以不安全的方式处理类型。 - Shafik Yaghmour
5个回答

10

这就是原因:

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

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


1 但这些检查只针对printf函数。如果您自己编写具有与printf相同签名的myprintf函数,则没有办法检查类型安全性,因为该签名使用省略号...,其中包含函数内部所有类型信息。


3
GCC的format属性可用于将相同的检查应用于您自己的myprintf函数。 - Jonathan Wakely
1
你的示例不会导致缓冲区溢出!!!它只会打印字符串地址。 - LS_ᴅᴇᴠ

8
printf系列函数是可变参数函数,它们都使用省略号...,表示可以将任何类型的参数传递给该函数。编译器没有任何限制,对参数的类型也没有要求,因此编译器不能强制执行任何类型安全规则,因为省略号...允许所有类型。函数使用一个格式字符串来假设参数类型(即使存在不匹配!)。当运行时读取和解释格式字符串时,编译器已经无法进行任何操作,因为代码已经编译。因此,在这种情况下,它不是类型安全的。 通常所谓的类型安全是指编译器能够通过对(未计算的)表达式类型施加规则来检查程序的类型一致性。 请注意,如果存在不匹配(函数无法判断!),程序就会进入未定义行为区域,程序的行为是不可预测的,理论上可以发生任何事情。
同样的逻辑适用于任何可变参数函数,例如scanf系列函数。

3
  1. the type system guarantees correctness with std::ostream but not printf. Konrad's answer is one example, but something like

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

    is also broken (assuming long and int are different sizes on your system). It may even work with one build target and fail with another. Trying to print typedefs like size_t has the same problem.

    This is (somewhat) solved with the the diagnostic provided by some compilers, but that doesn't help with the second sense:

  2. both type systems (the run-time type system used in the format string, and the compile-time system used in your code) cannot be kept in sync automatically. For example, printf interacts badly with templates:

    template <typename T> void print(T t) { printf("%d\n",t); }
    

    you can't make this correct for all types T- the best you can do is static_cast<int>(t) so it will fail to compile if T is not convertible to int. Compare

    template <typename T> void print(std::ostream& os, T t) { os << t << '\n'; }
    

    which selects the correct overload of operator<< for any T that has one.


2

通常,编译器无法检查printf的参数,甚至不会检查参数计数,也不会检查它们是否适合格式字符串。它们被“优化”来执行该工作,但这是一种特殊情况,可能会失败。 例如:

printf("%s %d\n", 1, "two", 3);

这段代码在编译时会通过(除非优化编译器检测到错误),运行时,printf函数将第一个参数(1)视为字符串,第二个参数(“two”)视为整数。printf甚至不会注意到有第三个参数,也不会注意到参数不足的情况!

使用cout时,编译器必须为每个插入的变量选择特定的operator<<操作符。 例如:

cout<<1<<"two"<<3<<endl;

编译器必须在调用相应的 ostream&operator<<(int)ostream&operator<<(const char*)(以及 ostream&operator<<(ios&(*)(ios&)))时进行更改。

由于没有格式字符串的运行时解释,cout 也会更快。


1

来自C++ FAQ

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

[...]

更加类型安全:使用 <iostream>,编译器可以静态地知道被 I/O 的对象的类型。相比之下,<cstdio> 使用“%”字段动态确定类型。

[...]

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接