在 C++ 中,我是否为我没有吃的东西付钱?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/52442415/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:47:11  来源:igfitidea点击:

In C++, am I paying for what I am not eating?

c++c

提问by Saher

Let's consider the following hello world examples in C and C++:

让我们考虑以下 C 和 C++ 中的 hello world 示例:

main.c

main.c

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

main.cpp

main.cpp

#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}

When I compile them in godbolt to assembly, the size of the C code is only 9 lines (gcc -O3):

当我在 Godbolt 中将它们编译为汇编时,C 代码的大小只有 9 行 ( gcc -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

But the size of the C++ code is 22 lines (g++ -O3):

但是 C++ 代码的大小是 22 行 ( g++ -O3):

.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

... which is much larger.

......这是更大的。

It is famous that in C++ you pay for what you eat. So, in this case, what am I paying for?

众所周知,在 C++ 中,您为所吃的东西付费。那么,在这种情况下,我付出了什么?

采纳答案by Arash

What you are paying for is to call a heavy library (not as heavy as printing into console). You initialize an ostreamobject. There are some hidden storage. Then, you call std::endlwhich is not a synonym for \n. The iostreamlibrary helps you adjusting many settings and putting the burden on the processor rather than the programmer. This is what you are paying for.

你付出的代价是调用一个沉重的库(不像打印到控制台那么重)。你初始化一个ostream对象。有一些隐藏的存储。然后,您调用std::endlwhich 不是 的同义词\n。该iostream库可帮助您调整许多设置并将负担推给处理器而不是程序员。这就是你要付出的代价。

Let's review the code:

让我们回顾一下代码:

.LC0:
        .string "Hello world"
main:

Initializing an ostream object + cout

初始化一个 ostream 对象 + cout

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

Calling coutagain to print a new line and flush

cout再次调用以打印新行并刷新

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Static storage initialization:

静态存储初始化:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

Also, it is essential to distinguish between the language and the library.

此外,区分语言和库也很重要。

BTW, this is just a part of the story. You do not know what is written in the functions you are calling.

BTW,这只是故事的一部分。你不知道你正在调用的函数中写了什么。

回答by Vittorio Romeo

So, in this case, what am I paying for?

那么,在这种情况下,我付出了什么?

std::coutis more powerful and complicated than printf. It supports things like locales, stateful formatting flags, and more.

std::coutprintf. 它支持语言环境、有状态格式标志等。

If you don't need those, use std::printfor std::puts- they're available in <cstdio>.

如果您不需要这些,请使用std::printfstd::puts- 它们在<cstdio>.



It is famous that in C++ you pay for what you eat.

众所周知,在 C++ 中,您为所吃的东西付费。

I also want to make it clear that C++ !=The C++ Standard Library. The Standard Library is supposed to be general-purpose and "fast enough", but it will often be slower than a specialized implementation of what you need.

我还想说清楚 C++ !=The C++ Standard Library。标准库应该是通用的并且“足够快”,但它通常比您需要的专门实现慢。

On the other hand, the C++ language strives to make it possible to write code without paying unnecessary extra hidden costs (e.g. opt-in virtual, no garbage collection).

另一方面,C++ 语言努力使编写代码而无需支付不必要的额外隐藏成本(例如选择加入virtual、无垃圾收集)成为可能。

回答by pschill

You are not comparing C and C++. You are comparing printfand std::cout, which are capable of different things (locales, stateful formatting, etc).

您不是在比较 C 和 C++。您正在比较printfand std::cout,它们具有不同的功能(语言环境、有状态格式等)。

Try to use the following code for comparison. Godbolt generates the same assembly for both files (tested with gcc 8.2, -O3).

尝试使用以下代码进行比较。Godbolt 为两个文件生成相同的程序集(使用 gcc 8.2,-O3 测试)。

main.c:

主文件:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

主.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}

回答by Konrad Rudolph

Your listings are indeed comparing apples and oranges, but not for the reason implied in most other answers.

您的列表确实在比较苹果和橙子,但不是出于大多数其他答案中暗示的原因。

Let's check what your code actually does:

让我们检查一下你的代码实际上做了什么:

C:

C:

  • print a single string, "Hello world\n"
  • 打印单个字符串, "Hello world\n"

C++:

C++:

  • stream the string "Hello world"into std::cout
  • stream the std::endlmanipulator into std::cout
  • 将字符串流式传输"Hello world"std::cout
  • std::endl操纵器流式传输到std::cout

Apparently your C++ code is doing twice as much work. For a fair comparison we should combine this:

显然你的 C++ 代码做了两倍的工作。为了公平比较,我们应该结合这一点:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

… and suddenly your assembly code for mainlooks very similar to C's:

……突然之间,您的汇编代码main看起来与 C 非常相似:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

In fact, we can compare the C and C++ code line by line, and there are very few differences:

其实我们可以逐行比较C和C++的代码,差别很小

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

The only real difference is that in C++ we call operator <<with two arguments (std::coutand the string). We could remove even that slight difference by using a closer C eqivalent: fprintf, which also has a first argument specifying the stream.

唯一真正的区别是在 C++ 中,我们operator <<使用两个参数(std::cout和字符串)进行调用。我们可以通过使用更接近的 C 等效项来消除即使是微小的差异fprintf,它还有一个指定流的第一个参数。

This leaves the assembly code for _GLOBAL__sub_I_main, which is generated for C++ but not C. This is the only true overhead that's visible in this assembly listing (there's more, invisible overhead for bothlanguages, of course). This code performs a one-time setup of some C++ standard library functions at the start of the C++ program.

这留下了 的汇编代码_GLOBAL__sub_I_main,它是为 C++ 而不是 C 生成的。这是此汇编列表中唯一可见的真正开销(当然,这两种语言还有更多不可见的开销)。此代码在 C++ 程序开始时执行一些 C++ 标准库函数的一次性设置。

But, as explained in other answers, the relevant difference between these two programs won't be found in the assembly output of the mainfunction since all the heavy lifting happens behind the scenes.

但是,正如其他答案中所解释的,这两个程序之间的相关差异不会在main函数的汇编输出中找到,因为所有繁重的工作都发生在幕后。

回答by el.pescado

It is famous that in C++ you pay for what you eat. So, in this case, what am I paying for?

众所周知,在 C++ 中,您为所吃的东西付费。那么,在这种情况下,我付出了什么?

That's simple. You pay for std::cout. "You pay for only what you eat" doesn't mean "you always get best prices". Sure, printfis cheaper. One can argue that std::coutis safer and more versatile, thus its greater cost is justified (it costs more, but provides more value), but that misses the point. You don't use printf, you use std::cout, so you pay for using std::cout. You don't pay for using printf.

这很简单。你付钱std::cout。“你只为你吃的东西付钱”并不意味着“你总是能得到最优惠的价格”。当然printf便宜了 有人可能会争辩std::cout说它更安全、更通用,因此它的更高成本是合理的(它成本更高,但提供更多价值),但这没有抓住重点。你不使用printf,你使用std::cout,所以你为使用付费std::cout。您无需为使用付费printf

A good example is virtual functions. Virtual functions have some runtime cost and space requirements - but only if you actuallyuse them. If you don't use virtual functions, you don't pay anything.

一个很好的例子是虚函数。虚拟函数有一些运行时成本和空间要求 - 但前提是您实际使用它们。如果您不使用虚函数,则无需支付任何费用。

A few remarks

几点说明

  1. Even if C++ code evaluates to more assembly instructions, it's still a handful of instructions, and any performance overhead is still likely dwarfed by actual I/O operations.

  2. Actually, sometimes it's even better than "in C++ you pay for what you eat". For example, compiler can deduce that virtual function call is not needed in some circumstances, and transform that into non-virtual call. That means you may get virtual functions for free. Isn't that great?

  1. 即使 C++ 代码评估为更多的汇编指令,它仍然是少数指令,并且任何性能开销仍然可能与实际的 I/O 操作相形见绌。

  2. 实际上,有时它甚至比“在 C++ 中你为所吃的东西付钱”更好。例如,编译器可以推断出在某些情况下不需要虚函数调用,并将其转换为非虚函数调用。这意味着您可以免费获得虚函数。那不是很好吗?

回答by álvaro Gustavo López

The "assembly listing for printf" is NOT for printf, but for puts (kind of compiler optimization?); printf is prety much more complex than puts... don't forget!

“printf 的汇编列表”不是用于 printf,而是用于 puts(某种编译器优化?);printf 比 puts 复杂得多......不要忘记!

回答by Marco Bonelli

I see some valid answers here, but I'm going to get a little bit more into the detail.

我在这里看到了一些有效的答案,但我将更深入地了解细节。

Jump to the summary below for the answer to your main question if you don't want to go through this entire wall of text.

如果您不想浏览整个文本墙,请跳转到下面的摘要以获取主要问题的答案。



Abstraction

抽象

So, in this case, what am I paying for?

那么,在这种情况下,我付出了什么?

You are paying for abstraction. Being able to write simpler and more human friendly code comes at a cost. In C++, which is an object-oriented language, almost everything is an object. When you use any object, three main things will always happen under the hood:

您正在为抽象付费。能够编写更简单、更人性化的代码是有代价的。在 C++ 这种面向对象的语言中,几乎所有东西都是对象。当您使用任何对象时,幕后总会发生三件主要事情:

  1. Object creation, basically memory allocation for the object itself and its data.
  2. Object initialization (usually via some init()method). Usually memory allocation happens under the hood as the first thing in this step.
  3. Object destruction (not always).
  1. 对象创建,基本上是为对象本身及其数据分配内存。
  2. 对象初始化(通常通过某种init()方法)。通常内存分配发生在幕后,作为这一步的第一件事。
  3. 对象销毁(并非总是如此)。

You don't see it in the code, but every single time you use an object all of the three above things need to happen somehow. If you were to do everything manually the code would obviously be way longer.

您在代码中看不到它,但是每次使用对象时,以上三件事都需要以某种方式发生。如果您要手动完成所有操作,代码显然会更长。

Now, abstraction can be made efficiently without adding overhead: method inlining and other techniques can be used by both compilers and programmers to remove overheads of abstraction, but this is not your case.

现在,可以在不增加开销的情况下有效地进行抽象:编译器和程序员都可以使用方法内联和其他技术来消除抽象的开销,但这不是您的情况。

What's really happening in C++?

C++ 到底发生了什么?

Here it is, broken down:

这是,分解:

  1. The std::ios_baseclass is initialized, which is the base class for everything I/O related.
  2. The std::coutobject is initialized.
  3. Your string is loaded and passed to std::__ostream_insert, which (as you already figured out by the name) is a method of std::cout(basically the <<operator) which adds a string to the stream.
  4. cout::endlis also passed to std::__ostream_insert.
  5. __std_dso_handleis passed to __cxa_atexit, which is a global function that is responsible for "cleaning" before exiting the program. __std_dso_handleitself is called by this function to deallocate and destroy remaining global objects.
  1. std::ios_base类初始化,这是一切的I / O相关的基类。
  2. std::cout对象被初始化。
  3. 您的字符串被加载并传递给std::__ostream_insert,它(正如您已经从名称中发现的那样)是std::cout(基本上是<<运算符)的一种方法,它将字符串添加到流中。
  4. cout::endl也传递给std::__ostream_insert.
  5. __std_dso_handle传递给__cxa_atexit,这是一个全局函数,负责在退出程序之前进行“清理”。__std_dso_handle这个函数调用它自己来释放和销毁剩余的全局对象。

So using C == not paying for anything?

所以使用 C == 不支付任何费用?

In the C code, very few steps are happening:

在 C 代码中,发生的步骤很少:

  1. Your string is loaded and passed to putsvia the ediregister.
  2. putsgets called.
  1. 您的字符串被加载并传递到puts通过edi寄存器。
  2. puts被调用。

No objects anywhere, hence no need to initialize/destroy anything.

任何地方都没有对象,因此不需要初始化/销毁任何东西。

This however doesn't mean that you're not "paying" for anything in C. You are still paying for abstraction, and also initialization of the C standard library and dynamic resolution the printffunction (or, actually puts, which is optimized by the compiler since you don't need any format string) still happen under the hood.

然而,这并不意味着您不会为 C 中的任何东西“付费”。您仍在为抽象付费,并且 C 标准库的初始化和printf函数的动态解析(或者,实际上puts,由于您不需要任何格式字符串,因此由编译器优化)仍然在幕后发生。

If you were to write this program in pure assembly it would look something like this:

如果你用纯汇编来编写这个程序,它看起来像这样:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

Which basically only results in invoking the writesyscallfollowed by the exitsyscall. Now thiswould be the bare minimum to accomplish the same thing.

这基本上只会导致调用write系统调用,然后exit调用系统调用。现在将是完成同样事情的最低要求。



To summarize

总结一下

C is way more bare-bone, and only does the bare minimum that is needed, leaving full control to the user, which is able to fully optimize and customize basically anything they want. You tell the processor to load a string in a register and then call a library function to use that string. C++ on the other hand is way more complex and abstract. This has enormous advantage when writing complicated code, and allows for easier to write and more human friendly code, but it obviously comes at a cost. There's always going to be a drawback in performance in C++ if compared to C in cases like this, since C++ offers more than what's needed to accomplish such basic tasks, and thus it adds more overhead.

C 更简单,只做所需的最低限度,将完全控制权留给用户,用户基本上可以完全优化和定制他们想要的任何东西。您告诉处理器在寄存器中加载一个字符串,然后调用库函数来使用该字符串。另一方面,C++ 更加复杂和抽象。这在编写复杂的代码时具有巨大的优势,并且可以更容易地编写和更人性化的代码,但这显然是有代价的。在这种情况下,如果与 C 相比,C++ 的性能总是会有缺陷,因为C++ 提供的不仅仅是完成这些基本任务所需的,因此它增加了更多的开销

Answering your main question:

回答你的主要问题

Am I paying for what I am not eating?

我是否为我没有吃的东西付钱?

In this specific case, yes. You are not taking advantage of anything that C++ has to offer more than C, but that's just because there's nothing in that simple piece of code that C++ could help you with: it is so simple that you really do not need C++ at all.

在这种特定情况下,是的。您没有利用 C++ 提供的比 C 更多的任何东西,但这只是因为 C++ 可以帮助您处理的那段简单代码中没有任何内容:它是如此简单以至于您根本不需要 C++。



Oh, and just one more thing!

哦,还有一件事!

The advantages of C++ may not look obvious at first glance, since you wrote a very simple and small program, but look at a little bit more complex example and see the difference (both programs do the exact same thing):

C++ 的优点乍一看可能并不明显,因为您编写了一个非常简单且小巧的程序,但是看看稍微复杂一点的示例,就会发现不同之处(两个程序都做完全相同的事情):

C:

ç

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C++:

C++

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

Hopefully you can clearly see what I mean here. Also notice how in C you have to manage memory at a lower level using mallocand freehow you need to be more careful about indexing and sizes, and how you need to be very specific when taking input and printing.

希望你能清楚地看到我在这里的意思。还要注意你如何用C必须在使用一个较低的水平,以管理内存malloc以及free如何需要更谨慎的索引和大小,并采取输入和打印时,你如何需要非常具体。

回答by Damon

There are a few misconceptions to start with. First, the C++ program does notresult in 22 instructions, it's more like 22,000 of them (I pulled that number from my hat, but it's approximately in the ballpark). Also, the C code doesn'tresult in 9 instructions, either. Those are only the ones you see.

有一些误解开始。首先,C++ 程序不会产生 22 条指令,它更像是 22,000 条指令(我从我的帽子里拿出了这个数字,但它大约在球场上)。此外,C 代码也不会产生 9 条指令。那些只是你看到的。

What the C code does is, after doing a lot of stuff that you don't see, it calls a function from the CRT (which is usually but not necessarily present as shared lib), then does notcheck for the return value or handle errors, and bails out. Depending on compiler and optimization settings it doesn't even really call printfbut puts, or something even more primitive.
You could have written more or less the same program (except for some invisible init functions) in C++ as well, if only you called that same function the same way. Or, if you want to be super-correct, that same function prefixed with std::.

C 代码所做的是,在做了很多你看不到的事情之后,它从 CRT 调用一个函数(通常但不一定作为共享库出现),然后检查返回值或句柄错误,并退出。根据编译器和优化设置,它甚至不会真正调用printfbut puts,或者更原始的东西。
您也可以用 C++ 编写或多或少相同的程序(除了一些不可见的 init 函数),只要您以相同的方式调用相同的函数。或者,如果你想要超级正确,同样的函数以std::.

The corresponding C++ code is in reality not at all the same thing. While the whole of <iostream>it is well-known for being a fat ugly pig that adds an immense overhead for small programs (in a "real" program you don't really notice that much), a somewhat fairer interpretation is that it does an awful lot of stuff that you don't see and which just works. Including but not limited to magical formatting of pretty much any haphazard stuff, including different number formats and locales and whatnot, and buffering, and proper error-handling. Error handling? Well yes, guess what, outputting a string can actually fail, and unlike the C program, the C++ program would notignore this silently. Considering what std::ostreamdoes under the hood, and without anyone getting aware of, it's actually pretty lightweight. Not like I'm using it because I hate the stream syntax with a passion. But still, it's pretty awesome if you consider what it does.

相应的 C++ 代码实际上根本不是一回事。虽然整个过程<iostream>以丑陋的胖猪为小程序增加了巨大的开销而闻名(在“真正的”程序中,你并没有真正注意到那么多),但更公平的解释是它做了一个可怕的很多你看不到的东西,它们只是有效。包括但不限于几乎任何杂乱无章的东西的神奇格式化,包括不同的数字格式和语言环境等等,以及缓冲和正确的错误处理。错误处理?嗯,是的,你猜怎么着,输出一个字符串实际上可能会失败,而且与 C 程序不同,C++ 程序不会默默地忽略这一点。考虑什么std::ostream在引擎盖下做,没有人知道,它实际上非常轻巧。不像我在使用它,因为我非常讨厌流语法。但是,如果您考虑它的作用,它仍然非常棒。

But sure, C++ overall is notas efficient as C can be. It cannot be as efficient since it is not the same thing and it isn't doingthe same thing. If nothing else, C++ generates exceptions (and code to generate, handle, or fail on them) and it gives some guarantees that C doesn't give. So, sure, a C++ program kinda necessarily needs to be a little bit bigger. In the big picture, however, this does not matter in any way. On the contrary, for realprograms, I've not rarely found C++ performing better because for one reason or another, it seems to lend for more favorable optimizations. Don't ask me why in particular, I wouldn't know.

但肯定的是,C ++总体是一样高效为C即可。它的效率不高,因为它不是一回事,也不是同样的事情。如果不出意外,C++ 会生成异常(以及生成、处理或失败的代码),并且它提供了 C 不提供的一些保证。所以,当然,C++ 程序一定需要更大一点。然而,从大局来看,这无论如何都无关紧要。相反,对于真正的程序,我很少发现 C++ 性能更好,因为出于某种原因,它似乎有助于进行更有利的优化。不要问我为什么特别,我不会知道。

If, instead of fire-and-forget-hope-for-the-best you care to write C code which is correct(i.e. you actually check for errors, and the program behaves correctly in presence of errors) then the difference is marginal, if existent.

如果,而不是一劳永逸,而是希望编写正确的C 代码(即您实际上检查错误,并且程序在出现错误时正确运行),那么差异是微不足道的,如果存在。

回答by KevinZ

You are paying for a mistake. In the 80s, when compilers aren't good enough to check format strings, operator overloading was seen as a good way to enforce some semblance of type safety during io. However, every one of its banner features are either implemented badly or conceptually bankrupt from the start:

你正在为一个错误付出代价。在 80 年代,当编译器不足以检查格式字符串时,运算符重载被视为在 io 期间强制执行某种类型安全的好方法。然而,它的每一个横幅功能从一开始要么实施得很糟糕,要么在概念上就破产了:

<iomanip>

<iomanip>

The most repugnant part of the C++ stream io api is the existence of this formatting header library. Besides being stateful and ugly and error prone, it couples formatting to the stream.

C++ 流 io api 最令人反感的部分是这个格式化头库的存在。除了有状态、丑陋和容易出错之外,它还将格式化与流耦合。

Suppose you want to print out an line with 8 digit zero filled hex unsigned int followed by a space followed by a double with 3 decimal places. With <cstdio>, you get to read a concise format string. With <ostream>, you have to save the old state, set alignment to right, set fill character, set fill width, set base to hex, output the integer, restore saved state (otherwise your integer formatting will pollute your float formatting), output the space, set notation to fixed, set precision, output the double and the newline, then restore the old formatting.

假设您想打印一行 8 位零填充十六进制 unsigned int 后跟一个空格,后跟一个带 3 个小数位的双精度数。使用<cstdio>,您可以阅读简洁的格式字符串。使用<ostream>,您必须保存旧状态,设置右对齐,设置填充字符,设置填充宽度,设置基数为十六进制,输出整数,恢复保存状态(否则您的整数格式会污染您的浮点格式),输出空间, 将 notation 设置为 fixed ,设置精度,输出双精度和换行符,然后恢复旧格式。

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Operator Overloading

运算符重载

<iostream>is the poster child of how not to use operator overloading:

<iostream>是如何不使用运算符重载的典型代表:

std::cout << 2 << 3 && 0 << 5;

Performance

表现

std::coutis several times slower printf(). The rampant featuritis and virtual dispatch does take its toll.

std::cout慢了好几倍printf()。猖獗的特征和虚拟调度确实造成了损失。

Thread Safety

线程安全

Both <cstdio>and <iostream>are thread safe in that every function call is atomic. But, printf()gets a lot more done per call. If you run the following program with the <cstdio>option, you will see only a row of f. If you use <iostream>on a multicore machine, you will likely see something else.

双方<cstdio><iostream>在每一个函数调用是原子线程安全的。但是,printf()每次调用可以完成更多工作。如果您使用该<cstdio>选项运行以下程序,您将只看到一行f. 如果您<iostream>在多核机器上使用,您可能会看到其他内容。

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

The retort to this example is that most people exercise discipline to never write to a single file descriptor from multiple threads anyway. Well, in that case, you'll have to observe that <iostream>will helpfully grab a lock on every <<and every >>. Whereas in <cstdio>, you won't be locking as often, and you even have the option of not locking.

对此示例的反驳是,大多数人都遵守纪律,无论如何都不要从多个线程写入单个文件描述符。好吧,在这种情况下,您必须注意这<iostream>将有助于锁定<<每个>>. 而在 中<cstdio>,您不会经常锁定,您甚至可以选择不锁定。

<iostream>expends more locks to achieve a less consistent result.

<iostream>花费更多的锁来获得不太一致的结果。

回答by Pharap

In addition to what all the other answers have said,
there's also the fact that std::endlis notthe same as '\n'.

除了什么都其他的答案说,
这里还有一个事实std::endl一样的'\n'

This is an unfortunately common misconception. std::endldoes not mean "new line",
it means "print new line and then flush the stream". Flushing is not cheap!

不幸的是,这是一个常见的误解。std::endl并不意味着“新行”,
它的意思是“打印新行然后刷新流”。冲洗并不便宜!

Completely ignoring the differences between printfand std::coutfor a moment, to be functionally eqvuialent to your C example, your C++ example ought to look like this:

完全无视之间的差异printfstd::cout片刻,待功能eqvuialent到C例如,你的C ++例子应该是这样的:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

And here's an example of what your examples should be like if you include flushing.

如果您包括冲洗,这里有一个示例,说明您的示例应该是什么样的。

C

C

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C++

C++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

When comparing code, you should always be careful that you're comparing like for likeand that you understand the implications of what your code is doing. Sometimes even the simplest examples are more complicated than some people realise.

在比较代码时,您应该始终小心比较,并且您了解代码所做的事情的含义。有时,即使是最简单的例子也比某些人意识到的要复杂。