How much overhead does C++ bring compared to straight C?

The other day, I had a conversation about putting C code onto an embedded chip. I wondered if it was possible to put C++ code on there. The gist of the conversation was that the C++ libraries had a lot of overhead and the executable size would be too large for the space available on the chip. I’m not an expert on exactly how much space is available on such devices (although I plan on becoming one), but I thought I’d play around with a few things and see just what overhead a c++ program would have when compared with it’s C counterparts.

I should note that there is already comparisons of the actual language constructs of C++ vs C for embedded devices. A simple google search yielded me a few answers. I’m more interested here in library overhead. For example, how does printf compare to cout, etc. All my experiments are done on my iMac with gcc/g++.

  1. The apples to apples test

    Just for a quick check, to make sure the other information is about the same, I compiled a simple do nothing program:

    test.c

    int main(int argc, char* argv[]) {
     int x=3;
     x+=5;
     --x;
     return 0;
    }
    
    gcc -o test test.c
    

    I then copied this file to test.cpp, and used g++ instead.

    >g++ -o test2 test.cpp
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12564 Aug  9 08:12 test
    -rwxr-xr-x  1 dennis  dennis  12564 Aug  9 08:12 test2
    

    Wait, slow down there. 12564 bytes exactly for each program? I thought for a second that perhaps gcc and g++ are creating the same output for the assembler. Here is a quick tip for checking the assembly generated by gcc or g++: Use the -S feature, which tells the compiler to stop after the assembly generation.

    gcc -o test.asm -S test.c
    

    I verified that the two asm files are indeed different. One looks much like a c program and the other looks like a c++ program:

    test.asm

    	.text
    .globl _main
    _main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$24, %esp
    	movl	$3, -12(%ebp)
    	leal	-12(%ebp), %eax
    	addl	$5, (%eax)
    	leal	-12(%ebp), %eax
    	decl	(%eax)
    	movl	$0, %eax
    	leave
    	ret
    	.subsections_via_symbols
    

    test2.asm

    	.text
    	.align 1,0x90
    .globl _main
    _main:
    LFB2:
    	pushl	%ebp
    LCFI0:
    	movl	%esp, %ebp
    LCFI1:
    	subl	$24, %esp
    LCFI2:
    	movl	$3, -12(%ebp)
    	leal	-12(%ebp), %eax
    	addl	$5, (%eax)
    	leal	-12(%ebp), %eax
    	decl	(%eax)
    	movl	$0, %eax
    	leave
    	ret
    LFE2:
    	.globl _main.eh
    _main.eh = 0
    .no_dead_strip _main.eh
    	.constructor
    	.destructor
    	.align 1
    	.subsections_via_symbols
    

    Ok then, the assembler takes these two different files and just happens to produce programs that are the exact same number of bytes. Good enough for me. On to step 2.

  2. IO Functions

    The 1st thing I’d like to check is how much overhead is produced by a simple include of the IO libraries.

    #include 
    
    > ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12564 Aug  9 14:04 test
    -rwxr-xr-x  1 dennis  dennis  12856 Aug  9 14:05 test2
    

    You get a few objects, cin, cout, et. al., with the C++ version so that doesn’t surprise me that the C++ version is slightly larger. Of course, the C version isn’t doing anything yet so I’ll compare again after printing something. Also of note here, if I #include <cstdio>, the c++ version is still the same code size as the c version.

    printf ( "The result stored in x is %d.\n" , x );
    
    std::cout "The result stored in x is " << x << '.' << std::endl;
    
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12588 Aug  9 14:11 test
    -rwxr-xr-x  1 dennis  dennis  13164 Aug  9 14:11 test2
    

    The gap widens. I'm assuming if I used cstdio and printf in the C++ program, that I'd still have the same sized executable with C++.

  3. How about some string operations

    Lets say I'd like to do some string operations.

    #include 
    
    #include  // note there is cstring also just like there is cstdio
    

    A quick compilation check produced the same sized programs as before. Including string doesn't increase code size in the C++ program the same as iostream does.

    Now, since string is a template class, I'm guessing that the size will further increase here.

    char* test_string="I'd like to test this string.";
    printf ( "%s\n", test_string );
    
    std::string test_string("I'd like to test this string.");
    std::cout << test_string << std::endl;
    
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12612 Aug  9 14:19 test
    -rwxr-xr-x  1 dennis  dennis  13416 Aug  9 14:19 test2
    

    The C program increased 24 bytes. The C++ program increased 252 bytes. The gap widens further.

    I'm going to add another string and compare the two:

    char* string2 = "This is another string.";
    printf ( "comparison: %d\n" , strcmp ( test_string, string2 ) );
    
    std::string string2("This is another string.");
    std::cout << "comparison: " << (test_string==string2) << std::endl;
    
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12636 Aug  9 14:23 test
    -rwxr-xr-x  1 dennis  dennis  13548 Aug  9 14:26 test2
    

    Ok, another 24 bytes for the C program and another 132 bytes for the C++ program. I actually thought that since the string template was already included, there might be some savings on calling the operator ==, but after thinking it through, that operator wouldn't be compiled into the program unless it was used. I also thought their might be more overhead for strcmp. I haven't looked into where that went.

  4. Classes

    One of the main things people talk about when working with C++ is the ability to group data into objects and take an object oriented approach to your program. Of course, you can do this with C too by just creating methods that take and operate on structs.

    typedef struct {
     int x, y;
    } some_class;
    
    int add(some_class* sc) {
     return sc->x + sc->y;
    }
    // ..snip..
    some_class sc;
    sc.x=3;
    sc.y=5;
    printf ( "class output: %d.\n" , add(&sc));
    
    class some_class {
     public:
      int x,y;
      int add() { return x+y;}
    };
    // .. snip ..
    some_class sc;
    sc.x=3;
    sc.y=5;
    std::cout << "class output: " << sc.add() << '.' << std::endl;
    
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12652 Aug  9 14:57 test
    -rwxr-xr-x  1 dennis  dennis  13588 Aug  9 14:56 test2
    

    Well, there you have it. 16 more bytes for the C program and 44 more bytes for the C++ program. Not terribly a lot, but I guess it is more. I think you'd have to do a lot of test with larger classes and larger programs to get a better picture though.

  5. Compiler Optimization

    A last thought I had was to compare code size if the compiler optimizes a few things for size. I found that on Darwin, you can use -Oz to optimize only for size, even at the expense of speed sometimes. Here goes:

    >gcc -o test -Oz test.c
    >g++ -o test2 -Oz test.cpp
    >ls -l test test2
    -rwxr-xr-x  1 dennis  dennis  12628 Aug  9 14:33 test
    -rwxr-xr-x  1 dennis  dennis  13448 Aug  9 14:33 test2
    

    Looks like we get -24 bytes for the C program and the C++ program lost 144 bytes.

I think my verdict is that it depends. The C++ program benefitted more from the optimization than the C program. (Noting that the C program was practically at it's minimal size already though.) The final difference for these little simple programs is only 820 bytes. I think it depends on the amount of space available and what exact functions you'd need I guess. I'm not convinced you couldn't go ahead and use C++ to make a nice little app on an embedded chip though.

I'll post more about this as I gain experience with it.

This entry was posted in Programming and tagged , , . Bookmark the permalink.

2 Responses to How much overhead does C++ bring compared to straight C?

  1. Levi says:

    Lots of people use C++ in embedded chips. You just have to be a little careful about what C++ features you use. As your first program attests, C++ is almost 100% backwards compatible with C. Although there are some differences in the labels of the original program as compiled in C and C++, you’ll notice that the actual instructions generated are exactly the same.

  2. agtrier says:

    IMH experience, the main difference is that C++ makes it *easier* to use existing class libraries rather than re-implementing all the features yourself, and thus skip a lot of implicit optimisations that you would otherwise have done.

    This is where most of your binary file size comes from. The other differences are neglectable (well, most of the time..)

Comments are closed.