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

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.


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

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


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.

Tags:

C++

C