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

后端 未结 13 1931
既然无缘
既然无缘 2020-12-12 12:39

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

main.c

#include 

int main()
{
    printf(\"Hello world\\n\");
    r         


        
相关标签:
13条回答
  • 2020-12-12 12:50

    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.

    0 讨论(0)
  • 2020-12-12 12:54

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-12 12:58

    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!

    0 讨论(0)
  • 2020-12-12 13:01

    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.

    0 讨论(0)
  • 2020-12-12 13:03

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

    0 讨论(0)
  • 2020-12-12 13:05

    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.

    0 讨论(0)
提交回复
热议问题