为什么即使在编译之后C和C ++也不同?

时间:2011-02-18 02:16:18

标签: c++ c compiler-theory

我猜对了,但仍然感到惊讶的是,这两个程序的输出,用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;
}

编译如下:

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

制作此代码:

C汇编

    .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 ++程序集

    .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 个答案:

答案 0 :(得分:19)

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

更重要的是,iostream处理很多事情stdio没有处理,因此显然会有一些实质性的开销。我理解,从理论上讲,这些可以编译成缩进的代码,但他们所做的并不是技术上相同的。

答案 1 :(得分:8)

这里的问题不是关于对象或优化:printfcout基本上是非常不同的野兽。要进行更公平的比较,请使用cout替换C ++代码中的printf语句。当你输出到stdout时,优化是一个没有实际意义的点,因为瓶颈肯定是终端的缓冲区。

答案 2 :(得分:6)

您没有像C示例那样在C ++示例中调用相同的函数。用简单的旧printf替换std :: cout管道,就像C代码一样,你应该看到两个编译器的输出之间有更大的相关性。

答案 3 :(得分:2)

你必须意识到C ++中还有很多“其他”的东西。 例如全局构造函数。图书馆也不同。

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

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

答案 4 :(得分:2)

  

我猜对了,但仍然感到惊讶的是,这两个程序的输出,用C和C ++编写,在编译时非常不同。

我很惊讶你很惊讶 - 他们的节目完全不同。

  

这让我觉得对象的概念在最低层仍然存在。

绝对......对象 是在程序执行期间布局和使用内存的方式(需要进行优化)。

  

这会增加开销吗?

不一定或通常 - 如果以相同的逻辑方式协调工作,相同的数据无论如何都必须在某处。

  

如果是这样,目前将面向对象的代码转换为程序样式或者很难做到这一点是不可能的优化?

该问题与OO与程序代码无关,或者与其他代码相比更有效。你在这里观察到的主要问题是C ++的ostreams需要更多的设置和拆卸,并且更多的I / O由内联代码协调,而printf()在预编译库中有更多的外联线,所以你在你的小代码清单中看不到它。目前尚不清楚哪个“更好”,除非你有性能问题,分析显示是相关的,你应该忘记它并完成一些有用的编程。

编辑回应评论:

公平的电话 - 措辞有点严厉 - 抱歉。这实际上是一个难以区分的......“只有编译器[知道]对象”在某种意义上是正确的 - 它们不是封装的,半神圣离散的“事物”,它们可以与程序员一样。而且,我们可以编写一个可以像使用cout一样使用的对象,它会在编译期间消失,并生成与printf()版本相同的代码。但是,cout和iostreams涉及一些设置,因为它是线程安全的,更内联并处理不同的语言环境,它是一个具有存储要求的真实对象,因为它带有更多关于错误状态的独立信息,无论你是否想要抛出异常,结束-of-file条件(printf()影响“errno”,然后被下一个库/ OS调用破坏)....

您可能会发现更有见地的是比较在打印一个字符串时生成多少额外代码,因为代码量基本上是一些常量开销+一些每次使用量,后者{{1}基于代码的类型和格式,可以比printf()更高效或更高效。值得注意的是......

ostream

...是正确的,更类似于你的printf()语句... std::cout << "Hello world!\n"; 显式请求不必要的刷新操作,因为符合标准的C ++程序将在流消失时刷新并关闭其缓冲区范围无论如何(也就是说,今天有一个有趣的帖子,似乎有人的Microsoft VisualC ++编译器没有为他们做这些! - 值得关注但很难相信)。