171

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

main.c

#include <stdio.h>

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

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):

.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):

.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?

Saher
  • 1,207
  • 2
  • 7
  • 8
  • 3
    Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/180566/discussion-on-question-by-saher-in-c-am-i-paying-for-what-i-am-not-eating). – Samuel Liew Sep 22 '18 at 09:09
  • 1
    https://gcc.gnu.org/onlinedocs/gccint/Initialization.html – Hans Passant Sep 22 '18 at 10:19
  • 26
    Never heard the term `eat` associated with C++. I believe you mean: "You pay only for what you **use**"? – Giacomo Alzetta Sep 24 '18 at 08:23
  • 1
    One thing I didn't see mentioned and which _may_ have influence on the output is that iostreams can be configured to not sync with c-streams. By default, iostream _do_ sync with c-streams (which also means less performance and more overhead), but this can be shutdown. How much, if at all, this affects output I haven't tested. Thought this is important to mention. – Sebastian Mach Sep 24 '18 at 11:01
  • 7
    @GiacomoAlzetta, ...it's a colloquialism, applying the concept of an all-you-can-eat buffet. Using the more precise term is certainly preferable with a global audience, but as a native American English speaker, the title makes sense to me. – Charles Duffy Sep 24 '18 at 12:24
  • Also similar to [Does the C++ standard mandate poor performance for iostreams](https://stackoverflow.com/q/4340396/290182) – beldaz Sep 25 '18 at 06:19
  • @GiacomoAlzetta but I often hear the term `eat` in respect to memory leaks, and C/C++ are said to be very prone to it. – trolley813 Sep 25 '18 at 12:48
  • 5
    @trolley813 Memory leaks have nothing to do with the quote and OP question. The point of "You pay only for what you use"/"You don't pay for what you don't use" is to say that no performance hit is taken if you don't use a specific feature/abstraction. Memory leaks have nothing to do with this at all, and this only shows that the term `eat` is more ambiguous and should be avoided. – Giacomo Alzetta Sep 25 '18 at 15:06
  • Not sure why this has been closed ... but just my two cents: about two years ago (as well as 15 years ago) we started an embedded project from scratch and had to decide if we develop it in C or C++. Since resources are rare on this embedded platform, we developed two base-applications with same functionality in both languages. Result has been the same for 15 and for 2 years: the C++ application has been more bloated and slower. So for some reason the binaries are still less efficient as in C - also when no complex libraries are included. – Elmi Oct 01 '18 at 10:41
  • Can I have two more votes to reopen the question? Voting to close was a bad and a biased idea. – Saher Oct 02 '18 at 06:02
  • `ostream` exposes some of the internals of `printf` into areas where you would see it in the compiled Assembly (With constructors, stack spacing, endl, and destructors). That doesn't mean it's slower, or you're paying for something. The storage and flushing that Arash mentions happen in `printf` too, you just can't see it. – Nicholas Pipitone Oct 18 '18 at 21:12
  • You eat *a lot* more than you see, actually. In fact you are merely looking at the order, not at the food. To get all the food on your plate for you to see you must link statically, otherwise it will be fed piecemeal. The difference you see in the calls is negligible compared to the differences of what's called. – Peter - Reinstate Monica Oct 19 '18 at 04:09
  • 1
    LLVM completely banned iostreams because of its issues: https://llvm.org/docs/CodingStandards.html#include-iostream-is-forbidden. Just like exceptions and RTTI btw since they are the other parts of C++ that don't adhere to the only pay for what you use principle. – Trass3r Oct 19 '18 at 05:08
  • @CharlesDuffy And as someone who does not use American English it makes perfect sense to me too. I happen to be a literal thinker even. It's just an analogy though. You could use any number of words because it's the context that answers the question for those who aren't sure of what it means. Context changes everything. – Pryftan Dec 02 '19 at 14:54

13 Answers13

211

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

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

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


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

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.

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).

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 5
    +1 for saying _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._ Many seem to blithely use STL components without considering the performance implications vs rolling your own. – Craig Estey Oct 17 '18 at 22:54
  • 7
    @Craig OTOH many parts of the standard library are usually faster and more correct than what one could typically produce instead. – Peter - Reinstate Monica Oct 19 '18 at 04:41
  • 4
    @PeterA.Schneider OTOH, when the STL version is 20x-30x slower, rolling your own is good thing. See my answer here: https://codereview.stackexchange.com/questions/191747/c-interview-problem-finding-an-island-in-a-2d-grid/191921#191921 Therein, others also suggested [at least a partial] roll your own. – Craig Estey Oct 23 '18 at 02:12
  • 1
    @CraigEstey A vector is (apart from the initial dynamic allocation which can be significant, depending on how much work will be done eventually with a given instance) not less efficient than a C array; it is *designed* not to be. Care must be taken not to copy it around, reserve enough space initially etc, but all that must be done with an array as well, and less safely. With respect to your linked example: Yes, a vector of vectors will (unless optimized away) incur an extra indirection compared to a 2D array, but I assume that the 20x efficiency is not rooted there but in the algorithm. – Peter - Reinstate Monica Oct 23 '18 at 08:15
177

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

Try to use the following code for comparison. Godbolt generates the same assembly for both files (tested with 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:

#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);
    }
}
pschill
  • 5,055
  • 1
  • 21
  • 42
134

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:

  • print a single string, "Hello world\n"

C++:

  • stream the string "Hello world" into std::cout
  • stream the std::endl manipulator into std::cout

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

#include <iostream>

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

… and suddenly your assembly code for main looks very similar to C’s:

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:

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::cout and 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.

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 both languages, of course). This code performs a one-time setup of some C++ standard library functions at the start of the C++ program.

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

melpomene
  • 84,125
  • 8
  • 85
  • 148
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 21
    Incidentally the C runtime *also* needs to be set up, and this happens in a function called `_start` but its code is part of the C runtime library. At any rate this happens for both C and C++. – Konrad Rudolph Sep 21 '18 at 15:45
  • If the program didn't end there, there would be a subtle difference: `stdin` is likely line-buffered, especially if it's connected to an interactive terminal, while `std::cout` is fully buffered. – Deduplicator Sep 21 '18 at 22:53
  • @Deduplicator Yes. I was tempted to include a comparison of flushing the streams (which printf does implicitly here) but it makes matters a lot more complicated. – Konrad Rudolph Sep 21 '18 at 23:17
  • 2
    @Deduplicator: Actually, by default the iostream library doesn't do *any* buffering of `std::cout` and instead passes I/O to the stdio implementation (which uses its own buffering mechanisms). In particular, when connected to (what is known to be) an interactive terminal, then by default you will never see fully buffered output when writing to `std::cout`. You have to explicitly disable synchronization with stdio if you want the iostream library to use its own buffering mechanisms for `std::cout`. –  Sep 21 '18 at 23:24
  • 6
    @KonradRudolph: Actually, `printf` need not flush the streams here. In fact, in a common use case (output redirected to file), you will usually find that `printf` statement *doesn't* flush. Only when output is line-buffered or unbuffered will the `printf` trigger a flush. –  Sep 21 '18 at 23:29
  • 1
    ... and then only if the string data printed by `printf` includes a newline, of course. +1 for pointing out that `std::endl` does a bunch of normally-useless work. Reading from `cin` flushes `cout` because they're linked by default, so you normally never need `endl`, and should avoid it for I/O performance and code-size reasons. – Peter Cordes Sep 23 '18 at 10:51
  • @PeterCordes: Actually, that's another misconception: reading from `cin` only flushes `cout` if the input buffer is empty. Even for interactive input, this assumption doesn't always hold. –  Sep 24 '18 at 07:50
  • 1
    (also, if you're writing output for interactive consumption, then "I/O performance" is not a relevant concern (unless you have a much more serious problem than a couple superfluous flushes)) –  Sep 24 '18 at 07:50
  • @Hurkyl: oh interesting. But still you can't block waiting for input with un-flushed output buffers, right? re: your 2nd argument about "I/O performance". You'd think so, but software components tend to be modular. The linked-streams behaviour means you can argue that `endl` isn't needed for correctness in a function that might be used for logging, or might be used to print an interactive prompt. Otherwise someone's going to make a correctness argument that you should always do the slow thing so this code is usable in every possible circumstance. And smaller code-size is always better. – Peter Cordes Sep 24 '18 at 07:55
  • @Hurkyl: You can also imagine a case like GNU `expect` that interacts with another another process, or maybe a network server that uses linked iostreams for input/output. A process that runs from `inetd` starts with stdin/stdout (`cin`/`cout`) connected to that socket. Print and read a response doesn't always mean interacting with a *human*. – Peter Cordes Sep 24 '18 at 07:58
  • 2
    @PeterCordes: Right, you can't block with unflushed output buffers, but you can run into the surprise where the program has accepted your input and marched on without displaying the expected output. I know this because I've had occasion to debug a "Help, my program is hanging during input but I can't figure out why!" that had given another developer fits for a few days. –  Sep 24 '18 at 08:01
  • 2
    @PeterCordes: The argument I make is "write what you mean" -- newlines are appropriate when you mean for output to be eventually available, and endl is appropriate when you mean for output to be available immediately. –  Sep 24 '18 at 08:05
  • 1
    @Hurkyl: I like that "write what you mean" point, good summary. I assume there's a C++ equivalent of `fflush(stdout)` which you can do to flush without printing anything, which you should use after calling a generic output function that formats something, if you need it visible right away. So generic functions that could be used in a non-interactive context should avoid `endl` in case that's *not* required and would be harmful. It compiles to 2 separate function calls, the character output and the flush. – Peter Cordes Sep 24 '18 at 08:08
  • 2
    @PeterCordes: Yes; there is a `std::flush` that is used in the same way as `std::endl`, or alternatively you can `std::cout.flush()`. –  Sep 24 '18 at 08:09
63

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

Let's review the code:

.LC0:
        .string "Hello world"
main:

Initializing an ostream object + 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 cout again to print a new line and flush

    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.

Arash
  • 2,114
  • 11
  • 16
  • 5
    As an additional note, thorough testing will show that prepending a C++ program with "ios_base::sync_with_stdio(false);" and "cin.tie(NULL);" will make cout faster than printf (Printf has format string overhead). The first eliminates overhead from making sure `cout; printf; cout` writes in order (Since they have their own buffers). The second will desync `cout` and `cin`, causing `cout; cin` to potentially ask the user for information first. Flushing will force it to sync only when you actually need it to. – Nicholas Pipitone Oct 16 '18 at 18:25
  • Hi Nicholas, thank you very much for adding these useful notes. – Arash Oct 17 '18 at 02:26
  • "it is essential to distinguish between the language and the library": Well yes, but the standard library coming with a language is the only one available everywhere, so it is the one being used everywhere (and yes, the C standard library is part of the C++ specification, so it can be used when desired). As to "You do not know what is written in the functions you are calling": You can link statically if you really want to know, and indeed the calling code which you examine is probably irrelevant. – Peter - Reinstate Monica Oct 19 '18 at 04:37
53

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

That's simple. You pay for std::cout. "You pay for only what you eat" doesn't mean "you always get best prices". Sure, printf is cheaper. One can argue that std::cout is 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.

A good example is virtual functions. Virtual functions have some runtime cost and space requirements - but only if you actually use 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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 6
    You don't get virtual functions for free. You still have to pay the cost of first writing them, and then debugging the compiler's transformation of your code when it doesn't match your idea of what it was supposed to do. – alephzero Sep 21 '18 at 18:36
  • 2
    @alephzero I'm not sure it's particularly relevant to compare development costs with performance costs. –  Sep 25 '18 at 15:43
  • Such a great opportunity for a pun wasted... You could have used the word 'calories' instead of 'price'. From that you could say that C++ is fatter than C. Or at least...the specific code in question (I'm biased against C++ in favour of C so I can't fairly go beyond). Alas. @Bilkokuya It might not be relevant in all cases but it's certainly something one shouldn't disregard. Thus it is relevant on the whole. – Pryftan Dec 02 '19 at 15:05
46

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!

  • 13
    This is so far the best answer, since all the others get hung up on a red herring about `std::cout`’s internals, which aren’t visible in the assembly listing. – Konrad Rudolph Sep 21 '18 at 14:39
  • 12
    The assembly listing is for a *call to* `puts`, which looks identical to a call to `printf` if you only pass a single format string and zero extra args. (except there will also be an `xor %eax,%eax` because we're passing zero FP args in registers to a variadic function.) Neither of these are the implementation, just passing a pointer to a string to the library function. But yes, optimizing `printf` to `puts` is something gcc does for formats that only have `"%s"`, or when there are no conversions, and the string ends with a newline. – Peter Cordes Sep 23 '18 at 10:52
45

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:

  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).

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++?

Here it is, broken down:

  1. The std::ios_base class is initialized, which is the base class for everything I/O related.
  2. The std::cout object 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::endl is also passed to std::__ostream_insert.
  5. __std_dso_handle is passed to __cxa_atexit, which is a global function that is responsible for "cleaning" before exiting the program. __std_dso_handle itself is called by this function to deallocate and destroy remaining global objects.

So using C == not paying for anything?

In the C code, very few steps are happening:

  1. Your string is loaded and passed to puts via the edi register.
  2. puts gets called.

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 printf function (or, actually puts, which is optimized by the compiler since you don't need any format string) still happen under the hood.

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 write syscall followed by the exit syscall. Now this would be the bare minimum to accomplish the same thing.


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.

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.


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:

#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++:

#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 malloc and free how you need to be more careful about indexing and sizes, and how you need to be very specific when taking input and printing.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
27

There are a few misconceptions to start with. First, the C++ program does not result 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't result in 9 instructions, either. Those are only the ones you see.

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 not check for the return value or handle errors, and bails out. Depending on compiler and optimization settings it doesn't even really call printf but 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::.

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 not ignore this silently. Considering what std::ostream does 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.

But sure, C++ overall is not as efficient as C can be. It cannot be as efficient since it is not the same thing and it isn't doing the 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 real programs, 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.

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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Damon
  • 67,688
  • 20
  • 135
  • 185
  • 16
    Very good answer, except that this assertion: “ But sure, C++ overall is not as efficient as C can be” is simply wrong. C++ can be as efficient as C, and sufficiently high-level code can be *more* efficient than equivalent C code. Yes, C++ has some overhead due to having to handle exceptions but on modern compilers the overhead of that is negligible compared to performance gains from better cost-free abstractions. – Konrad Rudolph Sep 21 '18 at 14:40
  • If I understood correctly, does `std::cout` throw exceptions too? – Saher Sep 21 '18 at 15:34
  • 6
    @Saher: Yes, no, maybe. `std::cout` is a `std::basic_ostream` and that one _can_ throw, and it _can_ rethrow otherwise-occurring exceptions if [configured](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions) to do so or it _can_ swallow exceptions. Thing is, stuff _can_ fail, and C++ as well as the C++ standard lib is (mostly) built so failures do not easily go unnoticed. This is an annoyance _and_ a blessing (but, more blessing than annoyance). C on the other hand just shows you the middle finger. You don't check a return code, you never know what happened. – Damon Sep 21 '18 at 17:57
  • 1
    @KonradRudolph: True, this is what I was trying to point out with _"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"_. It's not immediately obvious why, but not rarely it just optimizes better. For... whatever reason. You would think it's all the same to the optimizer, but it's not. – Damon Sep 21 '18 at 18:00
22

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:

<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.

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.

// <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:

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

Performance

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

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.

// 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> expends more locks to achieve a less consistent result.

KevinZ
  • 3,036
  • 1
  • 18
  • 26
  • 2
    Most implementations of printf have an extremely useful feature for localisation: numbered parameters. If you need to produce some output in two different languages (like English and French) and word order is different, you can use the same printf with a different formatting string, and it can print parameters in different order. – gnasher729 Sep 23 '18 at 07:49
  • 2
    That stateful formatting of streams must have given so many difficult to find bugs I don't know what to say. Great answer. Would upvote more than once if I could. – mathreadler Sep 23 '18 at 16:11
  • 6
    “`std::cout` is several times slower `printf()`” — This claim is repeated all over the net but it hasn’t been true in ages. Modern IOstream implementations perform on par with `printf`. The latter also performs virtual dispatch internally to deal with buffered streams and localised IO (done by the operating system but done nevertheless). – Konrad Rudolph Sep 23 '18 at 16:26
  • 1
    @KonradRudolph If you think that is true, you simply haven't benchmarked. Furthermore, you can tune the buffering with `setvbuf()`. You can skip the localization if you already have a string (using `fputs()`) or already have a buffer (using `fwrite()`), and modern compilers often does this lowering for you. You can even skip the file locking on linux if you are sure you are writing from a designated thread and you don't care about portability (using unlocked stdio). – KevinZ Sep 23 '18 at 18:00
  • @KevinZ Or maybe you haven’t? – Konrad Rudolph Sep 23 '18 at 18:26
  • 1
    @KonradRudolph I can't post my stuff, but I can quote others: https://github.com/fmtlib/fmt – KevinZ Sep 23 '18 at 18:28
  • 3
    @KevinZ And that’s great but it’s benchmarking one single, specific call, which showcases the specific strengths of fmt (lots of different formats in a single string). In more typical usage the difference between `printf` and `cout` shrinks. Incidentally there are tons of such benchmarks on this very site. – Konrad Rudolph Sep 23 '18 at 18:42
  • 3
    @KonradRudolph That's not true either. Microbenchmarks often underpredict the cost of bloat and indirection because they do not exhaust certain limited resources (be it registers, icache, memory, branch predictors) where a real program will. When you allude to "more typical usage", it's basically saying that you have considerably more bloat elsewhere, which is fine, but off topic. In my opinion, if you don't have performance requirements, you don't need to program in C++. – KevinZ Sep 23 '18 at 21:05
  • Your very first sentence made me smile. Thank you. And I totally agree with your idea that there is more bloat. Of course there is more bloat but many will disagree with that because they're biased. But the way I look at it is if it works for them so be it. As a long time mate put it (though about Unix): 'In Unix there are many ways to skin a *`cat`*' (though I changed it to be a pun). The same goes for programming. But still. Fit your requirements, make it modular and easy to maintain and be done with it. – Pryftan Oct 17 '18 at 12:17
18

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

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

Completely ignoring the differences between printf and std::cout for a moment, to be functionally eqvuialent to your C example, your C++ example ought to look like this:

#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

#include <stdio.h>

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

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 like and that you understand the implications of what your code is doing. Sometimes even the simplest examples are more complicated than some people realise.

Pharap
  • 3,826
  • 5
  • 37
  • 51
  • Actually, using `std::endl` ***is*** the functional equivalent to writing a newline to a line-buffered stdio stream. `stdout`, in particular, is required to be either line-buffered or unbuffered when connected to an interactive device. Linux, I believe, insists on the line-buffered option. –  Sep 25 '18 at 08:17
  • In fact, the iostream library *doesn't have* a line-buffered mode... the way to achieve the effect of line-buffering is precisely to use `std::endl` to output newlines. –  Sep 25 '18 at 08:24
  • @Hurkyl Insist? Then what is the use of *`setvbuf(3)`*? Or are you meaning to say the default is line buffered? FYI: *Normally all files are block buffered. If a stream refers to a terminal (as stdout normally does), it is line buffered. The standard error stream stderr is always unbuffered by default.* – Pryftan Oct 14 '18 at 00:06
  • Doesn't `printf` flush automatically upon encountering a newline character? – bool3max Oct 19 '18 at 23:31
  • @bool3max I can't find a mention of such a requirement on en.cppreference for either [C's printf](https://en.cppreference.com/w/c/io/fprintf) or [C++'s std::printf](https://en.cppreference.com/w/cpp/io/c/fprintf), so I'm going to assume it's left unspecified - i.e. it's implementation defined behaviour. (I don't have time to check the standard itself at the moment - it's quite large so it would probably take me quite some time.) – Pharap Oct 20 '18 at 00:26
  • @Pharap A quick example would be executing `printf` in really short intervals (without a trailing newline, say 10 times a second). You'll have to flush the stream manually if you want them to appear on `stdout` in real time. – bool3max Oct 20 '18 at 11:46
  • 1
    @bool3max That would only tell me what my environment does, it might be different in other environments. Even if it behaves the same in all of the most popular implementations, that doesn't mean there's an edge case somewhere. That's why the stanard is so important - the standard dictates whether something has to be the same for all implementations or whether it's allowed to vary between implementations. – Pharap Oct 21 '18 at 02:52
16

While the existing technical answers are correct, I think that the question ultimately stems from this misconception:

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

This is just marketing talk from the C++ community. (To be fair, there's marketing talk in every language community.) It doesn't mean anything concrete that you can seriously depend on.

"You pay for what you use" is supposed to mean that a C++ feature only has overhead if you're using that feature. But the definition of "a feature" is not infinitely granular. Often you will end up activating features that have multiple aspects, and even though you only need a subset of those aspects, it's often not practical or possible for the implementation to bring the feature in partially.

In general, many (though arguably not all) languages strive to be efficient, with varying degrees of success. C++ is somewhere on the scale, but there is nothing special or magical about its design that would allow it to be perfectly successful in this goal.

Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • 1
    There are just two things I can think about where you pay for something you don't use: exceptions and RTTI. And I don't think it's marketing talk; C++ is basically a more powerful C, which is also "don't pay for what you use". – Rakete1111 Sep 21 '18 at 20:44
  • 2
    @Rakete1111 It is long established that if exceptions don't throw, they don't cost. If your program is throwing consistently, it should be redesigned. If the condition of failure is out of your control, you should check for the condition with a bool returning sanity check, before calling the method that relies on the condition's being false. – schulmaster Sep 21 '18 at 21:40
  • @schulmaster Oh no I completely agree. But using exceptions does incur a cost, even if you never throw anything (in terms of executable size for example). – Rakete1111 Sep 21 '18 at 21:44
  • @Rakete1111 I was thinking more in terms of runtime performance, not necessarily in terms of optimal hospitality. Presumably, the code used to handle catastrophe in C would take up some .txt space, but likely less than most exceptions, yes. Ultimately, if things go wrong when you want them to go right, there is always a cost. The exception model, if not abused, is invaluable for long-uptime programs IMO. – schulmaster Sep 21 '18 at 21:52
  • 1
    @schulmaster: Exceptions can impose design constraints when code written in C++ needs to interact with code written in other languages, since non-local transfers of control can only work smoothly across modules if the modules know how to coordinate with each other. – supercat Sep 22 '18 at 19:04
  • 1
    *(though arguably not all) languages strive to be efficient*. Definitely not all: Esoteric programming languages strive to be novel / interesting, not efficient. https://esolangs.org/. Some of them, like BrainFuck, are famously inefficient. Or for example, the Shakespeare Programming Language, 227 bytes minimum size (codegolf) to [Print all integers](https://codegolf.stackexchange.com/a/93623). Out of languages intended for production use, most do aim for efficiency, but some (like bash) aim mostly for convenience and are known to be slow. – Peter Cordes Sep 23 '18 at 11:20
  • 2
    Well, it is marketing but it is almost completely true. You can stick to `` and not include ``, just like how you can compile with `-fno-exceptions -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables`. – KevinZ Sep 23 '18 at 18:12
11

The Input / Output functions in C++ are elegantly written and are designed so they are simple to use. In many respects they are a showcase for the object-orientated features in C++.

But you do indeed give up a bit of performance in return, but that's negligible compared to the time taken by your operating system to handle the functions at a lower level.

You can always fall back to the C style functions as they are part of the C++ standard, or perhaps give up portability altogether and use direct calls to your operating system.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 23
    "The Input / Output functions in C++ are hideous monsters struggling to hide their Cthulian nature behind a thin veneer of usefulness. In many respects they are a showcase for how not to design modern C++ code". Would probably be more accurate. – user673679 Sep 21 '18 at 14:43
  • 3
    @user673679: Very true. The great issue with C++ I/O streams is what's underneath: there's really **a lot** of complexity going on, and anyone who's ever dealt with them (I'm refering to `std::basic_*stream` downwards) knows the incoming eadaches. They were designed to be widely general and extended through inheritance; but nobody eventually did that, becuase of their complexity (there's literally books written on iostreams), so much that new libraries were born just for that (e.g. boost, ICU etc). I doubt we'll ever stop paying for this mistake. – edmz Sep 22 '18 at 11:34
1

As you have seen in other answers, you pay when you link in general libraries and call complex constructors. There is no particular question here, more a gripe. I'll point out some real-world aspects:

  1. Barne had a core design principle to never let efficiency be a reason for staying in C rather than C++. That said, one needs to be careful to get these efficiencies, and there are occasional efficiencies that always worked but were not 'technically' within the C spec. For example, the layout of bit fields was not really specified.

  2. Try looking through ostream. Oh my god its bloated! I wouldn't be surprised to find a flight simulator in there. Even stdlib's printf() usally runs about 50K. These aren't lazy programmers: half of the printf size was to do with indirect precision arguments that most people never use. Almost every really constrained processor's library creates its own output code instead of printf.

  3. The increase in size is usually providing a more contained and flexible experience. As an analogy, a vending machine will sell a cup of coffee-like-substance for a few coins and the whole transaction takes under a minute. Dropping into a good restaurant involves a table setting, being seated, ordering, waiting, getting a nice cup, getting a bill, paying in your choice of forms, adding a tip, and being wished a good day on your way out. Its a different experience, and more convenient if you are dropping in with friends for a complex meal.

  4. People still write ANSI C, though rarely K&R C. My experience is we always compile it with a C++ compiler using a few configuration tweaks to limit what is dragged in. There are good arguments for other languages: Go removes the polymorphic overhead and crazy preprocessor; there have been some good arguments for smarter field packing and memory layout. IMHO I think any language design should start with a listing of goals, much like the Zen of Python.

It's been a fun discussion. You ask why can't you have magically small, simple, elegant, complete, and flexible libraries?

There is no answer. There will not be an answer. That is the answer.

Charles Merriam
  • 19,908
  • 6
  • 73
  • 83