7

I guessed it but was still surprised to see that the output of these two programs, written in C and C++, when compiled were very different. That makes me think that the concept of objects still exist at even the lowest level. Does this add overhead? If so is it currently an impossible optimization to convert object oriented code to procedural style or just very hard to do?

helloworld.c

#include <stdio.h> int main(void) { printf("Hello World!\n"); return 0; } 

helloworld.cpp

#include <iostream> int main() { std::cout << "Hello World!" << std::endl; return 0; } 

Compiled like this:

gcc helloworld.cpp -o hwcpp.S -S -O2 gcc helloworld.c -o hwc.S -S -O2 

Produced this code:

C assembly

 .file "helloworld.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "Hello World!\n" .text .p2align 4,,15 .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, 4(%esp) movl $1, (%esp) call __printf_chk xorl %eax, %eax leave ret .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits 

C++ assembly

 .file "helloworld.cpp" .text .p2align 4,,15 .type _GLOBAL__I_main, @function _GLOBAL__I_main: .LFB1007: .cfi_startproc .cfi_personality 0x0,__gxx_personality_v0 pushl %ebp .cfi_def_cfa_offset 8 movl %esp, %ebp .cfi_offset 5, -8 .cfi_def_cfa_register 5 subl $24, %esp movl $_ZStL8__ioinit, (%esp) call _ZNSt8ios_base4InitC1Ev movl $__dso_handle, 8(%esp) movl $_ZStL8__ioinit, 4(%esp) movl $_ZNSt8ios_base4InitD1Ev, (%esp) call __cxa_atexit leave ret .cfi_endproc .LFE1007: .size _GLOBAL__I_main, .-_GLOBAL__I_main .section .ctors,"aw",@progbits .align 4 .long _GLOBAL__I_main .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "Hello World!" .text .p2align 4,,15 .globl main .type main, @function main: .LFB997: .cfi_startproc .cfi_personality 0x0,__gxx_personality_v0 pushl %ebp .cfi_def_cfa_offset 8 movl %esp, %ebp .cfi_offset 5, -8 .cfi_def_cfa_register 5 andl $-16, %esp pushl %ebx subl $28, %esp movl $12, 8(%esp) movl $.LC0, 4(%esp) movl $_ZSt4cout, (%esp) .cfi_escape 0x10,0x3,0x7,0x55,0x9,0xf0,0x1a,0x9,0xfc,0x22 call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i movl _ZSt4cout, %eax movl -12(%eax), %eax movl _ZSt4cout+124(%eax), %ebx testl %ebx, %ebx je .L9 cmpb $0, 28(%ebx) je .L5 movzbl 39(%ebx), %eax .L6: movsbl %al,%eax movl %eax, 4(%esp) movl $_ZSt4cout, (%esp) call _ZNSo3putEc movl %eax, (%esp) call _ZNSo5flushEv addl $28, %esp xorl %eax, %eax popl %ebx movl %ebp, %esp popl %ebp ret .p2align 4,,7 .p2align 3 .L5: movl %ebx, (%esp) call _ZNKSt5ctypeIcE13_M_widen_initEv movl (%ebx), %eax movl $10, 4(%esp) movl %ebx, (%esp) call *24(%eax) jmp .L6 .L9: call _ZSt16__throw_bad_castv .cfi_endproc .LFE997: .size main, .-main .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 .weakref _ZL20__gthrw_pthread_oncePiPFvvE,pthread_once .weakref _ZL27__gthrw_pthread_getspecificj,pthread_getspecific .weakref _ZL27__gthrw_pthread_setspecificjPKv,pthread_setspecific .weakref _ZL22__gthrw_pthread_createPmPK14pthread_attr_tPFPvS3_ES3_,pthread_create .weakref _ZL20__gthrw_pthread_joinmPPv,pthread_join .weakref _ZL21__gthrw_pthread_equalmm,pthread_equal .weakref _ZL20__gthrw_pthread_selfv,pthread_self .weakref _ZL22__gthrw_pthread_detachm,pthread_detach .weakref _ZL22__gthrw_pthread_cancelm,pthread_cancel .weakref _ZL19__gthrw_sched_yieldv,sched_yield .weakref _ZL26__gthrw_pthread_mutex_lockP15pthread_mutex_t,pthread_mutex_lock .weakref _ZL29__gthrw_pthread_mutex_trylockP15pthread_mutex_t,pthread_mutex_trylock .weakref _ZL31__gthrw_pthread_mutex_timedlockP15pthread_mutex_tPK8timespec,pthread_mutex_timedlock .weakref _ZL28__gthrw_pthread_mutex_unlockP15pthread_mutex_t,pthread_mutex_unlock .weakref _ZL26__gthrw_pthread_mutex_initP15pthread_mutex_tPK19pthread_mutexattr_t,pthread_mutex_init .weakref _ZL29__gthrw_pthread_mutex_destroyP15pthread_mutex_t,pthread_mutex_destroy .weakref _ZL30__gthrw_pthread_cond_broadcastP14pthread_cond_t,pthread_cond_broadcast .weakref _ZL27__gthrw_pthread_cond_signalP14pthread_cond_t,pthread_cond_signal .weakref _ZL25__gthrw_pthread_cond_waitP14pthread_cond_tP15pthread_mutex_t,pthread_cond_wait .weakref _ZL30__gthrw_pthread_cond_timedwaitP14pthread_cond_tP15pthread_mutex_tPK8timespec,pthread_cond_timedwait .weakref _ZL28__gthrw_pthread_cond_destroyP14pthread_cond_t,pthread_cond_destroy .weakref _ZL26__gthrw_pthread_key_createPjPFvPvE,pthread_key_create .weakref _ZL26__gthrw_pthread_key_deletej,pthread_key_delete .weakref _ZL30__gthrw_pthread_mutexattr_initP19pthread_mutexattr_t,pthread_mutexattr_init .weakref _ZL33__gthrw_pthread_mutexattr_settypeP19pthread_mutexattr_ti,pthread_mutexattr_settype .weakref _ZL33__gthrw_pthread_mutexattr_destroyP19pthread_mutexattr_t,pthread_mutexattr_destroy .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits 
1
  • 5
    these are two different programs, the only thing they have in common is the final output on the screen. of course they have to compile differently. Commented Feb 18, 2011 at 2:39

5 Answers 5

19

Different compilers produce different code. An early version of gcc versus the current version of gcc likely produce different code.

More importantly, iostream handles a lot of things stdio doesn't, so there's obviously going to be some substantial overhead. I understand that, in theory, these could be compiled down to indentical code, but what they're doing is not technically identical.

Sign up to request clarification or add additional context in comments.

Comments

8

Your issue here isn't about objects or optimization: it's that printf and cout are fundamentally very different beasts. For a more fair comparison, replace your cout statement in the C++ code with printf. Optimization is a moot point when you're outputting to stdout, as the bottleneck will certainly be the terminal's buffer.

3 Comments

+1 for spotting the difference, but please note that stdout doesn't mean a terminal, and the overhead of cout often does negatively impact throughput when redirected to a disk file.
@Tony: True, but when redirecting to /dev/null, the function to beat isn't printf, but one that recognizes /dev/null and skips the formatting altogether.
@Ben Voigt: the point isn't about overhead, efficiency or anything. it's that the original programs are two different programs that do different things, they have to be compiled differently. if the same program was fed to both compilers the code would be more similar.
6

You're not calling the same functions in the C++ example as the C example. Replace the std::cout pipes with plain old printf just like the C code and you should see a much greater correlation between the output of the two compilers.

Comments

2

You have to realize that there are a whole lot of "other" things going on in C++. Global constructors for example. Also the libraries are different.

the C++ stream object is far more complicated than C io, and if you look through the assembler you can see all the code for pthreads in the C++ version.

It's not necessarily slower but it's certainly different.

Comments

2

I guessed it but was still surprised to see that the output of these two programs, written in C and C++, when compiled were very different.

I'm surprised you were surprised - they're totally different programs.

That makes me think that the concept of objects still exist at even the lowest level.

Absolutely... objects are the way memory is laid out and used during program execution (subject to optimisations).

Does this add overhead?

Not necessarily or typically - the same data would have to be somewhere anyway if the work was being coordinated in the same logical way.

If so is it currently an impossible optimization to convert object oriented code to procedural style or just very hard to do?

The issue has nothing to do with OO vs procedural code, or one being more efficient than the other. The main issue you observe here is that C++'s ostreams require a bit more setup and tear-down, and have more of the I/O coordinated by inline code, while printf() has more out-of-line in the precompiled library so you can't see it in your little code listing. It's not clear really which is "better", and unless you have a performance problem that profiling shows is related you should forget about it and get some useful programming done.

EDIT in response to comment:

Fair call - was a bit harshly worded - sorry. It's a difficult distinction to make actually... "only the compiler [knows] of objects" is true in one sense - they're not encapsulated, half-holy discrete "things" to the compiler the way they can be to the programmer. And, we could write an object that could be used exactly like you have used cout that would disappear during compilation and produce code that was equivalent to the printf() version. But, cout and iostreams involves some setup because it's thread safe and more inlined and handles different locales, and it's a real object with storage requirements because it carries around more independent information about error state, whether you want exceptions thrown, end-of-file conditions (printf() affects "errno", which is then clobbered by the next library/OS call)....

What you might find more insightful is to compare how much extra code is generated when you print one more string, as the amount of code is basically some constant overhead + some per-usage amount, and in latter regard ostream-based code can be as or more efficient than printf(), depending on the types and formatting requested. It's also worth noting that...

std::cout << "Hello world!\n"; 

...is correct and more analogous to your printf() statement... std::endl explicitly requests an unnecessary flushing operation, as a Standard-compliant C++ program will flush and close its buffers as the stream goes out of scope anyway (that said, there's an interesting post today where it seems someone's Microsoft VisualC++ compiler's not doing that for them! - worth keeping an eye on but hard to believe).

1 Comment

Why were you surprised? I don't claim to be an expert. The software industry is harsh... I was surprised because I figured under the hood cout was just taking a string and and outputting it to the console like the printf was with some differences, I didn't think an actual object had to be created, I thought only the compiler knew of objects.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.