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:
- Object creation, basically memory allocation for the object itself and its data.
- Object initialization (usually via some
init()
method). Usually memory allocation happens under the hood as the first thing in this step.
- 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:
- The
std::ios_base
class is initialized, which is the base class for everything I/O related.
- The
std::cout
object is initialized.
- 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.
cout::endl
is also passed to std::__ostream_insert
.
__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:
- Your string is loaded and passed to
puts
via the edi
register.
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.