为什么C和c++在编译后仍然不同?

时间:2021-09-07 23:40:59

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?

我猜到了,但还是惊讶地发现,这两个用C和c++编写的程序在编译时的输出非常不同。这使我认为对象的概念仍然存在于甚至最低的层次。这增加开销吗?如果是这样的话,那么现在将面向对象的代码转换成程序化的样式或者仅仅是很难做到这一点是不可能的吗?

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

5 个解决方案

#1


19  

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

不同的编译器产生不同的代码。gcc的早期版本与当前版本可能产生不同的代码。

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.

更重要的是,iostream能处理许多stdio不能处理的事情,所以显然会有一些重大的开销。我知道,在理论上,它们可以被编译成密码学的代码,但是它们所做的在技术上并不是完全相同的。

#2


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.

这里的问题不是对象或优化:而是printf和cout本质上是非常不同的东西。为了进行更公平的比较,请将cout语句替换为带有printf的c++代码。当您输出到stdout时,优化是没有意义的,因为瓶颈肯定是终端的缓冲区。

#3


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.

在c++示例中,您不会调用与C示例相同的函数。将std::cout管道替换为普通的旧printf,就像C代码一样,您应该会看到两个编译器的输出之间存在更大的相关性。

#4


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.

你必须意识到在c++中有很多其他的东西。全球构造函数为例。而且图书馆也不一样。

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.

c++流对象比C io复杂得多,如果您查看汇编程序,您可以看到c++版本中所有的pthreads代码。

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

它不一定慢,但肯定是不同的。

#5


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.

我猜到了,但还是惊讶地发现,这两个用C和c++编写的程序在编译时的输出非常不同。

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.

这个问题与OO和过程代码无关,或者两者之间的效率更高。您在这里观察到的主要问题是,c++的ostreams需要更多的设置和拆解,并有更多的I/O由内联代码协调,而printf()在预编译库中有更多的线外代码,因此您无法在您的小代码清单中看到它。现在还不清楚哪一个是“更好”的,除非您有性能问题,剖析显示与此相关,否则您应该忘记它,并完成一些有用的编程。

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

公平交易——这句话有点刺耳——对不起。这是很难区分的。“只有编译器[知道]对象”在某种意义上是正确的——它们对编译器来说不像对程序员来说那样是封装的、半神圣的离散的“东西”。并且,我们可以编写一个对象,它可以像您使用cout那样使用,在编译过程中消失,并生成与printf()版本相同的代码。但cout,iostream涉及了一些设置,因为它是线程安全的和更多的内联和处理不同地区,这是一个真正的对象和存储需求,因为它携带更独立的信息的错误状态,是否你想要抛出的异常,文件尾条件(printf()影响“errno”,然后因未来图书馆/ OS调用)....

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

你可能会发现更深刻的是比较有多少额外的代码生成打印一个字符串时,作为代码的数量基本上是一些常数开销+付费方式视使用数量,并在后者方面ostream-based代码可以比printf()或更有效率,这取决于类型和格式要求。同样值得注意的是……

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

…是正确的,更类似于您的printf()语句…endl显式地请求一个不必要的刷新操作,因为一个符合标准的c++程序将会刷新并关闭它的缓冲区,因为流超出了范围(就是说,今天有一个有趣的帖子,看起来某人的Microsoft visualc++编译器没有为他们做这个!)-值得关注,但难以置信)。

#1


19  

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

不同的编译器产生不同的代码。gcc的早期版本与当前版本可能产生不同的代码。

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.

更重要的是,iostream能处理许多stdio不能处理的事情,所以显然会有一些重大的开销。我知道,在理论上,它们可以被编译成密码学的代码,但是它们所做的在技术上并不是完全相同的。

#2


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.

这里的问题不是对象或优化:而是printf和cout本质上是非常不同的东西。为了进行更公平的比较,请将cout语句替换为带有printf的c++代码。当您输出到stdout时,优化是没有意义的,因为瓶颈肯定是终端的缓冲区。

#3


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.

在c++示例中,您不会调用与C示例相同的函数。将std::cout管道替换为普通的旧printf,就像C代码一样,您应该会看到两个编译器的输出之间存在更大的相关性。

#4


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.

你必须意识到在c++中有很多其他的东西。全球构造函数为例。而且图书馆也不一样。

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.

c++流对象比C io复杂得多,如果您查看汇编程序,您可以看到c++版本中所有的pthreads代码。

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

它不一定慢,但肯定是不同的。

#5


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.

我猜到了,但还是惊讶地发现,这两个用C和c++编写的程序在编译时的输出非常不同。

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.

这个问题与OO和过程代码无关,或者两者之间的效率更高。您在这里观察到的主要问题是,c++的ostreams需要更多的设置和拆解,并有更多的I/O由内联代码协调,而printf()在预编译库中有更多的线外代码,因此您无法在您的小代码清单中看到它。现在还不清楚哪一个是“更好”的,除非您有性能问题,剖析显示与此相关,否则您应该忘记它,并完成一些有用的编程。

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

公平交易——这句话有点刺耳——对不起。这是很难区分的。“只有编译器[知道]对象”在某种意义上是正确的——它们对编译器来说不像对程序员来说那样是封装的、半神圣的离散的“东西”。并且,我们可以编写一个对象,它可以像您使用cout那样使用,在编译过程中消失,并生成与printf()版本相同的代码。但cout,iostream涉及了一些设置,因为它是线程安全的和更多的内联和处理不同地区,这是一个真正的对象和存储需求,因为它携带更独立的信息的错误状态,是否你想要抛出的异常,文件尾条件(printf()影响“errno”,然后因未来图书馆/ OS调用)....

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

你可能会发现更深刻的是比较有多少额外的代码生成打印一个字符串时,作为代码的数量基本上是一些常数开销+付费方式视使用数量,并在后者方面ostream-based代码可以比printf()或更有效率,这取决于类型和格式要求。同样值得注意的是……

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

…是正确的,更类似于您的printf()语句…endl显式地请求一个不必要的刷新操作,因为一个符合标准的c++程序将会刷新并关闭它的缓冲区,因为流超出了范围(就是说,今天有一个有趣的帖子,看起来某人的Microsoft visualc++编译器没有为他们做这个!)-值得关注,但难以置信)。