7

私はそれを推測しましたが、C と C++ で書かれたこれら 2 つのプログラムをコンパイルしたときの出力が非常に異なっていることに驚きました。そう考えると、オブジェクトの概念は最低レベルでも存在していると思います。これによりオーバーヘッドが追加されますか? もしそうなら、オブジェクト指向コードを手続き型スタイルに変換することは現在不可能な最適化ですか、それとも非常に難しいですか?

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
4

5 に答える 5

19

異なるコンパイラは異なるコードを生成します。gcc の初期バージョンと現在のバージョンの gcc では、異なるコードが生成される可能性があります。

さらに重要なことはiostream、多くのことを処理stdioできないため、かなりのオーバーヘッドが明らかに発生することです。理論的には、これらは同じコードにコンパイルできることは理解していますが、それらが行っていることは技術的に同一ではありません。

于 2011-02-18T02:19:51.750 に答える
8

ここでの問題は、オブジェクトや最適化に関するものではありません。それはそれでprintfありcout、根本的に非常に異なる獣です。より公平に比較​​するcoutには、C++ コードのステートメントを に置き換えますprintf。ボトルネックは確かに端末のバッファになるため、標準出力に出力するときの最適化は重要なポイントです。

于 2011-02-18T02:20:34.863 に答える
6

C++ の例では、C の例と同じ関数を呼び出していません。std::cout パイプを、C コードと同じように単純な古い printf に置き換えると、2 つのコンパイラの出力間の相関関係が大幅に向上するはずです。

于 2011-02-18T02:23:16.280 に答える
2

C++ では「その他」のことがたくさん行われていることを認識しておく必要があります。たとえば、グローバルコンストラクター。また、ライブラリも異なります。

C++ ストリーム オブジェクトは C io よりもはるかに複雑です。アセンブラを調べると、C++ バージョンの pthreads のすべてのコードを確認できます。

必ずしも遅いわけではありませんが、確かに異なります。

于 2011-02-18T02:21:55.830 に答える
2

私はそれを推測しましたが、C と C++ で書かれたこれら 2 つのプログラムをコンパイルしたときの出力が非常に異なっていることに驚きました。

私はあなたが驚いたことに驚いています - それらは全く異なるプログラムです.

そう考えると、オブジェクトの概念は最低レベルでも存在していると思います。

絶対に...オブジェクト、メモリがレイアウトされ、プログラムの実行中に使用される方法です(最適化の対象)。

これによりオーバーヘッドが追加されますか?

必ずしも、または通常ではありません。作業が同じ論理的な方法で調整されている場合、とにかく同じデータがどこかにある必要があります。

もしそうなら、オブジェクト指向コードを手続き型スタイルに変換することは現在不可能な最適化ですか、それとも非常に難しいですか?

この問題は、オブジェクト指向と手続き型コード、または一方が他方よりも効率的であることとは何の関係もありません。ここで観察した主な問題は、C++ の ostream にはもう少しセットアップとティアダウンが必要であり、インライン コードによって調整される I/O が多いのに対し、printf() はプリコンパイル済みライブラリに多くのアウトオブラインがあるため、あなたの小さなコードリストには表示されません。どちらが「より良い」かは明確ではありません。プロファイリングが示すパフォーマンスの問題が関連していない限り、そのことは忘れて、役に立つプログラミングを行う必要があります。

コメントに応じて編集:

フェアコール - 少し厳しい言い方でした - 申し訳ありません。実際に区別するのは難しいです...「コンパイラーだけがオブジェクトを[知っている]」というのは、ある意味では真実です-それらはカプセル化されておらず、コンパイラーにとっては半聖なる個別の「もの」であり、プログラマーにとってそうである可能性があります. coutそして、コンパイル中に消えて printf() バージョンと同等のコードを生成する、使用したのとまったく同じように使用できるオブジェクトを作成できます。しかし、coutまた、iostreams は、スレッド セーフであり、よりインライン化されており、さまざまなロケールを処理するため、セットアップが必要です。また、エラー状態に関するより独立した情報を保持するため、ストレージ要件を持つ実際のオブジェクトであり、例外をスローするかどうか、ファイルの終わりの条件 (printf () は "errno" に影響し、次のライブラリ/OS 呼び出しによって上書きされます)...

コードの量は基本的に一定のオーバーヘッド + 使用ごとの量であり、後者に関しては、ostreamベースのコードは要求されたタイプとフォーマットによっては、printf() よりも効率的です。また、注目に値するのは...

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

...正しく、printf()ステートメントに似ています...std::endl標準に準拠したC++プログラムは、ストリームが範囲外になるとバッファをフラッシュして閉じるため、不必要なフラッシュ操作を明示的に要求します(つまり、今日の興味深い投稿では、誰かの Microsoft VisualC++ コンパイラがそのようなことをしていないようです! - 注目する価値はありますが、信じがたいことです)。

于 2011-02-18T02:26:41.713 に答える