Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c

People also ask

What is it called when you don't eat for a while?

Anorexia. If you get an anorexia diagnosis (known as anorexia nervosa), you're not eating enough food.

Is eating not hungry a fine?

Intuitive eating has been gaining traction as a method of eating that tunes into natural signals of hunger and fullness and puts pleasure and satisfaction when eating as the forefront goals.

What's the difference between eaten and eating?

The difference between the present perfect continuous ("I have been eating") and the present perfect simple ("I have eaten") is that the continuous form of the present perfect focuses on a process, something that has been happening over a recent period of time, while the simple form focuses on the end result of ...


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


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);
    }
}

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.


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.


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?