std::cout和printf有什么机器语言上的区别吗?

39 浏览
0 Comments

std::cout和printf有什么机器语言上的区别吗?

C++中的printf()cout有什么区别?

0
0 Comments

std::cout和printf之间有什么区别?这个问题的出现是因为有人在问题中声称std::cout比printf要好得多,即使问题只是问有什么区别。现在,确实有一个区别——std::cout是C++的,而printf是C的(然而,你可以在C++中使用它,就像几乎所有其他来自C的东西一样)。现在,说实话;无论是printf还是std::cout都有各自的优点。

真正的区别

可扩展性

std::cout是可扩展的。我知道有人会说printf也是可扩展的,但C标准中并没有提到这种扩展(所以你必须使用非标准特性——但甚至没有普通的非标准特性存在),而且这样的扩展只有一个字母(所以很容易与已经存在的格式冲突)。

与printf不同,std::cout完全依赖于操作符重载,因此自定义格式不会有问题——你所要做的就是定义一个子程序,将std::ostream作为第一个参数,将你的类型作为第二个参数。因此,没有命名空间问题——只要你有一个类(不限于一个字符),你就可以为它编写工作的std::ostream重载。

然而,我怀疑很多人都不会想要扩展ostream(老实说,我很少看到这样的扩展,即使它们很容易制作)。但是,如果你需要,它就在这里。

语法

正如大家可以很容易地注意到的,printf和std::cout使用了不同的语法。printf使用了使用模式字符串和可变长度参数列表的标准函数语法。实际上,printf是C拥有可变长度参数列表的原因——printf的格式太复杂,如果没有可变长度参数列表就无法使用。然而,std::cout使用了不同的API——返回自身的<<运算符。

一般来说,这意味着C版本会更短,但在大多数情况下并不重要。当你打印许多参数时,差异是显而易见的。如果你需要写像"Error 2: File not found."这样的内容,假设错误号和它的描述是占位符,代码将如下所示。两个示例的功能基本相同(好吧,有点不同,std::endl实际上刷新了缓冲区)。

printf("Error %d: %s.\n", id, errors[id]);

std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;

虽然这看起来不太疯狂(只是长了两倍),但当你实际格式化参数而不仅仅是打印它们时,事情就变得更疯狂了。例如,打印像"0x0424"这样的内容是非常疯狂的。这是因为std::cout混合了状态和实际值。我从未见过一种语言中类似std::setfill的东西会是一个类型(当然除了C++)。printf明显区分了参数和实际类型。与iostream版本相比,我真的更喜欢维护printf版本(即使它看起来有点神秘),因为后者包含了太多的噪音。

printf("0x%04x\n", 0x424);

std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;

翻译

这是printf的真正优势所在。printf格式字符串是一个字符串。这使得它非常容易翻译,而iostream滥用<<运算符的方式就不一样了。假设gettext()函数用于翻译,并且你想显示"Error 2: File not found.",之前显示的格式字符串的翻译代码将如下所示:

printf(gettext("Error %d: %s.\n"), id, errors[id]);

现在,假设我们要翻译成Fictionish,其中错误号在描述之后。翻译后的字符串将类似于%2$s oru %1$d.\n。现在,在C++中如何实现呢?好吧,我不知道。我猜你可以制作一个伪造的iostream,在其中构造一个可以传递给gettext的printf,或者其他什么的,以用于翻译的目的。当然,$不是C标准,但它非常常见,我认为使用它是安全的。

不必记住/查找特定整数类型的语法

C有很多整数类型,C++也是如此。std::cout为你处理了所有类型,而printf则需要根据整数类型使用特定的语法(也有非整数类型,但在实践中,你只会使用printf的一个非整数类型——const char *(C字符串,可以使用std::string的to_c方法获得))。例如,要打印size_t,你需要使用%zu,而int64_t则需要使用%"PRId64"。这些表格可以在http://en.cppreference.com/w/cpp/io/c/fprintf和http://en.cppreference.com/w/cpp/types/integer上找到。

无法打印NUL字节\0

因为printf使用C字符串而不是C++字符串,所以它不能直接打印NUL字节,除非使用特殊的技巧。在某些情况下,可以使用%c和'\0'作为参数,虽然这显然是一个技巧。

没人在乎的区别

性能

更新:事实证明iostream太慢了,以至于通常比你的硬盘还慢(如果你将程序重定向到文件)。如果你需要输出大量数据,可以禁用与stdio的同步。如果性能是真正的问题(而不仅仅是向STDOUT写入几行),那就使用printf吧。

每个人都认为他们关心性能,但却没人费心去衡量它。我的回答是,无论你使用printf还是iostream,I/O都是瓶颈。我认为从快速查看汇编(使用clang编译,使用-O3编译器选项)中,printf可能会更快。假设我的错误示例,printf示例执行的调用比cout示例要少得多。这是一个使用printf的main函数:

main:

@ BB#0:

push {lr}

ldr r0, .LCPI0_0

ldr r2, .LCPI0_1

mov r1, #2

bl printf

mov r0, #0

pop {lr}

mov pc, lr

.align 2

@ BB#1:

你可以轻松注意到,两个字符串和2(数字)作为printf的参数被推送。就这些了,没有其他东西。作为对比,这是编译为汇编的iostream。不,这里没有内联;每个单独的<<运算符调用都意味着另一个调用,带有另一组参数。

main:

@ BB#0:

push {r4, r5, lr}

ldr r4, .LCPI0_0

ldr r1, .LCPI0_1

mov r2, #6

mov r3, #0

mov r0, r4

bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l

mov r0, r4

mov r1, #2

bl _ZNSolsEi

ldr r1, .LCPI0_2

mov r2, #2

mov r3, #0

mov r4, r0

bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l

ldr r1, .LCPI0_3

mov r0, r4

mov r2, #14

mov r3, #0

bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l

ldr r1, .LCPI0_4

mov r0, r4

mov r2, #1

mov r3, #0

bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l

ldr r0, [r4]

sub r0, r0, #24

ldr r0, [r0]

add r0, r0, r4

ldr r5, [r0, #240]

cmp r5, #0

beq .LBB0_5

@ BB#1: @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit

ldrb r0, [r5, #28]

cmp r0, #0

beq .LBB0_3

@ BB#2:

ldrb r0, [r5, #39]

b .LBB0_4

.LBB0_3:

mov r0, r5

bl _ZNKSt5ctypeIcE13_M_widen_initEv

ldr r0, [r5]

mov r1, #10

ldr r2, [r0, #24]

mov r0, r5

mov lr, pc

mov pc, r2

.LBB0_4: @ %_ZNKSt5ctypeIcE5widenEc.exit

lsl r0, r0, #24

asr r1, r0, #24

mov r0, r4

bl _ZNSo3putEc

bl _ZNSo5flushEv

mov r0, #0

pop {r4, r5, lr}

mov pc, lr

.LBB0_5:

bl _ZSt16__throw_bad_castv

.align 2

@ BB#6:

然而,老实说,这没什么意义,因为I/O无论如何都是瓶颈。我只是想说明iostream不是因为"类型安全"而更快。大多数C实现使用计算跳转来实现printf格式,因此printf的速度已经尽可能快了,即使编译器不知道printf(并不是说它们不知道——一些编译器可以在某些情况下优化printf——常量字符串以\n结尾通常会被优化为puts)。

继承

我不知道为什么你想要继承ostream,但我不在乎。使用FILE也是可能的。

class MyFile : public FILE {}

类型安全

确实,可变长度参数列表没有类型安全,但这并不重要,因为流行的C编译器可以检测到printf格式字符串的问题,只要你启用了警告。事实上,Clang可以在不启用警告的情况下进行检测。

$ cat safety.c

#include

int main(void) {

printf("String: %s\n", 42);

return 0;

}

$ clang safety.c

safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]

printf("String: %s\n", 42);

~~ ^~

%d

1 warning generated.

$ gcc -Wall safety.c

safety.c: In function ‘main’:

safety.c:4:5: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]

printf("String: %s\n", 42);

^

你说I/O无论如何都是瓶颈。显然你从未测试过这个假设。我引用自己的话:"另一方面,iostream的速度为75.3MB/s,无法缓冲数据以跟上硬盘的速度。这很糟糕,而且它甚至还没有做任何真正的工作。我认为我的I/O库应该能够饱和我的磁盘控制器时,我的期望不高。

我非常喜欢这个答案的很多方面,但也许我最喜欢的是"每个人都认为他们关心性能,但却没有人费心去衡量它"。

0
0 Comments

在这篇文章中,作者测试了使用coutprintf两种输出方式的性能差异。作者首先测试了不同情况下使用coutprintf输出新行的性能,结果显示printf的性能要比cout高。然后作者测试了使用coutprintf输出包含字符串常量和其他内容的性能,结果显示coutprintf的性能相差无几。作者得出结论,如果只需要输出新行,使用printf会更快,否则使用cout几乎与printf的速度相当,甚至更快。

文章中提到了一些关于性能差异的原因。首先,printf会在一次调用中输出所有参数,而cout的每个<<操作都会产生一个单独的调用。在作者的测试中,只输出一个参数和一个新行,所以看不到差异。其次,printf可能会在内部调用许多辅助函数来处理不同的格式指定符,而cout的调用可能会被编译器内联。然而,由于编译器的优化,这些因素对性能几乎没有影响。

还有一些解决方法。他建议使用sprintffprintf来代替printf,使用stringstreamfstream来代替cout。他认为这些函数可能会更快,因为它们可以更好地优化和内联。

总之,作者试图通过实际数据来消除关于coutprintf性能差异的一些误解。他认为根据实际数据做出决策比凭空猜测更重要。他还指出,cout具有类型安全和可读性等明显优势。最后,他提供了完整的测试代码,以供读者参考。

0
0 Comments

从这段内容中可以看出,问题的出现原因是对于使用哪种输出方法(std::cout vs printf)存在不同的观点和需求。一方面,使用std::cout(即C++的iostream库)可以增加类型安全性、减少错误、提供扩展性和可继承性。另一方面,使用printf具有更快的速度和更简洁易用的特点。解决这个问题的方法是通过对比std::cout和printf的功能和特点,根据具体需求选择合适的输出方法。

std::cout相对于printf的优势在于:

1. 更加类型安全:使用std::cout,编译器可以静态地知道正在进行输入输出的对象的类型。而printf使用“%”字段来动态确定类型。

2. 减少错误:使用std::cout,不需要与实际对象进行一致的冗余“%”标记。减少了一类错误。

3. 可扩展性:C++的iostream机制允许用户定义新的类型进行输入输出,而不会破坏现有的代码。而如果每个人都在printf中同时添加新的不兼容的“%”字段,将会造成混乱。

4. 可继承性:C++的iostream机制是由真正的类(如std::ostream和std::istream)构建的,而不是像printf的FILE*一样。这意味着可以创建其他用户定义的看起来和行为像流的东西,并且可以做任何奇怪和奇妙的事情。这样就可以使用其他用户编写的大量I/O代码,而无需了解你的“扩展流”类。

而使用printf的优势在于速度更快、更简洁易用。在某些特定和有限的情况下,可能会因为性能原因而选择使用printf而不是std::cout。

解决这个问题的方法是首先进行性能分析,根据具体情况选择合适的输出方法。另外,还可以考虑使用FastFormat库,该库提供了类型安全性、表达能力和性能的结合,可以作为std::cout和printf的替代方案。

总结起来,选择使用std::cout还是printf取决于具体需求和优先考虑的因素,包括类型安全性、减少错误、可扩展性、可继承性和性能等。进行性能分析,并考虑使用FastFormat库,可以帮助我们做出更好的选择。

0