Let\'s consider the following hello world examples in C and C++:
main.c
#include
int main()
{
printf(\"Hello world\\n\");
r
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:
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.
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.
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.
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.
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);
}
}
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!
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.
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.