printf-错误的来源?[关闭]


9

我在代码中使用了大量的printf跟踪/记录功能,发现这是编程错误的根源。我总是发现插入运算符(<<)有点奇怪,但我开始认为通过使用它可以避免其中一些错误。

有人曾经有过类似的启示吗,或者我只是在这里抓住稻草?

一些带走点

  • 我当前的思路是类型安全胜过使用printf的任何好处。真正的问题是格式字符串和使用非类型安全的可变参数函数。
  • 也许我不会使用<<stl输出流变体,但我肯定会研究使用非常相似的类型安全机制。
  • 许多跟踪/日志记录都是有条件的,但我希望始终运行代码以免遗漏测试中的错误,因为这是一个很少采用的分支。

4
printf在C ++世界中?我在这里想念什么吗?
user827992 2012年

10
@ user827992:您是否缺少C ++标准通过引用包含C标准库的事实?printf在C ++中使用是完全合法的。(是否有个好主意是另一个问题。)
Keith Thompson

2
@ user827992:printf确实有一些优势;看我的答案。
基思·汤普森

1
这个问题很临界。“你们怎么想”的问题通常是封闭的。
dbracey 2012年

1
我猜@vitaut(感谢小费)。我对激进的节制感到有些困惑。它并没有真正引起人们对编程情况的有趣讨论,而这正是我想要了解的更多内容。
约翰·莱德格伦

Answers:


2

printf,尤其是在您可能关心性能的情况下(例如sprintf和fprintf)是一个非常奇怪的技巧。令我感到惊讶的是,由于与虚拟函数相关的微妙性能开销而对C ++产生强烈兴趣的人们将继续捍卫C的io。

是的,为了弄清楚我们的输出格式(在编译时我们可以100%知道的东西),让我们在运行时使用难以理解的格式代码在一个巨大的怪异跳转表中解析一个fricken格式字符串!

当然,不能使这些格式代码与它们表示的类型相匹配,这太容易了……而且每次查找时都会提醒您,是%llg还是%lg这种强类型语言使您手动找出类型以打印/扫描某些内容,并且是针对32位之前的处理器设计的。

我承认,C ++对格式宽度和精度的处理非常庞大,并且可能会使用一些语法糖,但这并不意味着您必须捍卫C的主要io系统的怪异之处。两种语言的绝对基础都非常简单(尽管您可能应该始终使用自定义错误功能/错误流进行调试代码),中度情况类似于C中的正则表达式(易于编写,难以解析/调试) ),而复杂的情况在C中是不可能的。

(如果您完全使用标准容器,请给自己写一些快速的模板化操作符<<重载,这些重载可以使您执行诸如std::cout << my_list << "\n";debug之类的操作,其中my_list是type list<vector<pair<int,string> > >。)


1
标准C ++库的问题是,大多数化身都是operator<<(ostream&, T)通过调用...来实现的sprintf!的性能sprintf不是最佳的,但是因此,iostream的性能通常会更差。
Jan Hudec

@JanHudec:到目前为止,这已经有十年了。实际的打印是通过相同的底层系统调用完成的,而C ++实现通常为此调用C库...但是,这与通过printf路由std :: cout并不相同。
jkerian 2012年

16

将C风格printf()(或puts()or putchar()或...)输出与C ++风格的std::cout << ...输出混合可能是不安全的。如果我没记错的话,它们可以有单独的缓冲机制,因此输出可能不会按预期的顺序出现。(正如AProgrammer在评论中提到的那样,sync_with_stdio解决了此问题)。

printf()从根本上讲是类型不安全的。参数的预期类型由格式字符串确定("%d"需要使用int或提升为的东西int"%s"需要char*必须指向正确终止的C样式字符串等),但是传递错误的参数类型会导致未定义的行为,而不是可诊断的错误。某些编译器(例如gcc)在警告类型不匹配方面做得相当好,但是只有格式字符串是文字或在编译时是已知的(这是最常见的情况)时,它们才可以这样做。该语言不需要警告。如果您传递了错误的参数类型,则可能会发生任何不好的事情。

另一方面,C ++的流I / O具有更多的类型安全性,因为<<运算符对于许多不同的类型都是重载的。std::cout << x不必指定类型x; 无论哪种类型,编译器都会生成正确的代码x

另一方面,printf恕我直言,的格式选项要方便得多。如果我想打印一个小数点后3位数字的浮点值,则可以使用"%.3f"-,即使在同一printf调用中,它也不会影响其他参数。setprecision另一方面,C ++ 会影响流的状态,如果您不太谨慎地将流恢复到其先前的状态,则C ++ 可能会使以后的输出混乱。(这是我的个人宠儿;如果我缺少避免这种情况的干净方法,请发表评论。)

两者都有优点和缺点。printf如果您碰巧具有C背景并且更加熟悉它,或者将C源代码导入C ++程序,则可用性特别有用。std::cout << ...对于C ++来说更惯用了,不需要太多注意就可以避免类型不匹配。两者都是有效的C ++(C ++标准通过引用包括了大多数C标准库)。

可能是最好使用std::cout << ...谁可能在你的代码工作的其他C ++程序员的缘故,但你可以使用一个-尤其是在跟踪代码,你会扔掉。

当然,值得花一些时间来学习如何使用调试器(但这在某些环境中可能不可行)。


在原始问题中没有提及混合。
dbracey 2012年

1
@dbracey:不,但我认为值得一提的是printf
基思·汤普森

6
有关同步问题,请参见std::ios_base::sync_with_stdio
AProgrammer 2012年

1
+1使用std :: cout在多线程应用中打印调试信息是100%无用的。至少对于printf而言,人或机器不太可能交错和无法解析事物。
詹姆斯

@James:那是因为std::cout对打印的每个项目使用单独的调用吗?您可以通过在打印之前将输出行收集到字符串中来解决此问题。当然,您也可以一次打印一份printf;在一个呼叫中打印一行(或更多行)更加方便。
基思·汤普森

2

您的问题很可能来自两个截然不同的标准输出管理器的混合,每个输出管理器都有自己的计划来处理这个小小的STDOUT。您无法保证它们的实现方式,它们完全有可能设置有冲突的文件描述符选项,并且两者都尝试对它执行不同的操作,等等。此外,插入运算符具有以下一个主要优点printfprintf您可以这样做:

printf("%d", SomeObject);

<<不会。

注意:要进行调试,请不要使用printfcout。您使用fprintf(stderr, ...)cerr


在原始问题中没有提及混合。
dbracey 2012年

当然,您可以打印一个对象的地址,但是最大的区别是它printf不是类型安全的,而我目前的思路是类型安全胜过使用的任何好处printf。问题实际上是格式字符串,而不是类型安全的可变参数函数。
约翰·莱德格伦

@JohnLeidegren:但是,如果SomeObject不是指针怎么办?我们将获得编译器决定的任意二进制数据表示SomeObject
Linuxios 2012年

我想我向后读了您的答案... nvm。
约翰·莱德格伦

1

有很多群组(例如google)不喜欢串流。

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(弹出三角形会很麻烦,因此您可以看一下讨论。)我认为Google C ++样式指南中有很多非常明智的建议。

我认为要权衡的是流更安全,但printf更易于阅读(更容易获得所需的格式)。


2
Google样式指南不错,但其中包含许多不适合通用指南的项目。(这没关系,因为毕竟这是Google的在/针对Google运行代码的指南。)
Martin Ba

1

printf由于缺乏类型安全性,可能会导致错误。有寻址而无需切换到几方面iostream<<操作,更复杂的格式:

  • 某些编译器(例如GCC和Clang)可以选择printf根据printf参数检查格式字符串,并且如果不匹配,则可以显示警告,例如以下内容。
    警告:转换指定的类型为“ int”,但参数的类型为“ char *”
  • typesafeprintf脚本可以您进行预处理printf风格的来电,使他们类型安全的。
  • 诸如Boost.FormatFastFormat之类的让您使用类似printf格式的字符串(特别是Boost.Format几乎与相同printf),同时保持iostreams'类型安全性和类型可扩展性。

1

Printf语法基本上很好,减去了一些晦涩的输入。如果您认为C#,Python和其他语言为何使用非常相似的结构是错误的?C或C ++中的问题:它不是语言的一部分,因此,编译器不会检查其语法是否正确(*),并且如果进行速度优化,也不会分解为一系列本机调用。请注意,如果优化大小,printf调用可能会更有效!C ++流语法不尽如人意。它可行,类型安全在那儿,但是冗长的语法……令人讨厌。我的意思是我用它,但没有喜悦。

(*)一些编译器会执行此检查以及几乎所有的静态分析工具(我使用Lint,从此以后printf从未有任何问题)。


1
Boost.Format库,它结合了方便的语法(format("fmt") % arg1 % arg2 ...;)与类型安全。以更高的性能为代价,因为它会生成字符串流调用,而该字符串流调用会在许多实现中内部生成sprintf调用。
Jan Hudec

0

printf我个人认为,它是处理变量的输出工具,比任何CPP流输出都要灵活得多。例如:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

但是,<<当您为特定方法重载CPP 运算符时,可能想使用CPP 运算符...例如,获取包含特定人的数据的对象的转储PersonData...。

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

为此,说起来会更加有效(假设a是PersonData的对象)

std::cout << a;

比:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

前者更符合封装原理(无需了解具体信息,私有成员变量),并且也更易于阅读。


0

您不应该printf在C ++中使用。曾经 正如您正确指出的那样,原因是错误的根源,而且打印自定义类型(在C ++中几乎所有内容都应为自定义类型)的事实令人痛苦。C ++解决方案是流。

但是,存在一个关键问题,使流不适合任何可见的输出!问题是翻译。从gettext手册中借用的示例说您要编写:

cout << "String '" << str << "' has " << str.size() << " characters\n";

现在,德语翻译出现了,并说:好吧,用德语,信息应该是

Ñ贼臣郎IST死Zeichenkette“ 小号

而现在,您遇到了麻烦,因为他需要重新整理各个部分。应该说,甚至许多实现printf对此都有问题。除非他们支持扩展,否则您可以使用

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Boost.Format库支持printf风格的格式,有这个功能。所以你写:

cout << format("String '%1' has %2 characters\n") % str % str.size();

不幸的是,它会带来一些性能损失,因为在内部它会创建一个字符串流并使用<<运算符格式化每个位,并且在许多实现中,<<运算符会内部调用sprintf。我怀疑,如果真的需要,可以实现更有效的实施。


-1

您正在做很多无用的工作,除了stl邪恶与否之外,您还可以通过一系列调试代码来进行调试,这些代码printf只会增加1级的可能失败。

只需使用调试器,然后阅读一些有关异常以及如何捕获和引发它们的知识;尽量不要过于冗长。

聚苯乙烯

printf 在C中使用,对于C ++ std::cout


您不使用跟踪/日志记录来代替调试器。
约翰·莱德格伦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.