7

我猜到了,但仍然惊讶地发现这两个用 C 和 C++ 编写的程序在编译时的输出非常不同。这让我觉得对象的概念即使在最低层次上仍然存在。这会增加开销吗?如果是这样,目前将面向对象的代码转换为过程风格是不可能的优化,还是很难做到?

你好世界.c

#include <stdio.h>

int main(void) {
    printf("Hello World!\n");
    return 0;
}

你好世界.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
4

5 回答 5

19

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

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

于 2011-02-18T02:19:51.750 回答
8

printf您在这里的问题与对象或优化无关:它cout是根本不同的野兽。为了更公平的比较,请将coutC++ 代码中的语句替换为printf. 当您输出到标准输出时,优化是一个有争议的问题,因为瓶颈肯定是终端的缓冲区。

于 2011-02-18T02:20:34.863 回答
6

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

于 2011-02-18T02:23:16.280 回答
2

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

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

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

于 2011-02-18T02:21:55.830 回答
2

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

我很惊讶你很惊讶——它们是完全不同的程序。

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

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

这会增加开销吗?

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

如果是这样,目前将面向对象的代码转换为过程风格是不可能的优化,还是很难做到?

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

编辑回应评论:

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

您可能会发现比较有洞察力的是,当您再打印一个字符串时会生成多少额外代码,因为代码量基本上是一些恒定的开销 + 一些每次使用量,而在后者方面,ostream基于代码的代码可以是比 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).

于 2011-02-18T02:26:41.713 回答