Почему C и C ++ различаются даже после компиляции? - PullRequest
7 голосов
/ 18 февраля 2011

Я догадался об этом, но все же был удивлен, увидев, что результаты этих двух программ, написанных на 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 ]

19 голосов
/ 18 февраля 2011

Разные компиляторы выдают разный код. Ранняя версия gcc по сравнению с текущей версией gcc, вероятно, создала другой код.

Что еще более важно, iostream обрабатывает много вещей, stdio - нет, так что, очевидно, будут существенные накладные расходы. Я понимаю, что теоретически их можно скомпилировать в идентичный код, но то, что они делают, технически не идентично.

8 голосов
/ 18 февраля 2011

Ваша проблема здесь не в объектах или оптимизации: дело в том, что printf и cout принципиально разные звери.Для более точного сравнения замените ваш оператор cout в коде C ++ на printf.Оптимизация - это спорный вопрос, когда вы выводите на стандартный вывод, поскольку узким местом, безусловно, будет буфер терминала.

6 голосов
/ 18 февраля 2011

Вы не вызываете те же функции в примере C ++, что и в примере C. Замените каналы std :: cout старым простым printf, как и код C, и вы увидите гораздо большую корреляцию между выходными данными двух компиляторов.

2 голосов
/ 18 февраля 2011

Я догадался об этом, но все же был удивлен, увидев, что результаты этих двух программ, написанных на C и C ++, при компиляции были очень разными.

Я удивлен, что вы были удивлены - это совершенно разные программы.

Это заставляет меня думать, что концепция объектов все еще существует даже на самом низком уровне.

Абсолютно ... объекты - это способ размещения и использования памяти во время выполнения программы (при условии оптимизации).

Это добавляет накладных расходов?

Не обязательно или обычно - одни и те же данные должны были бы быть где-то в любом случае, если бы работа координировалась таким же логическим способом.

Если это так, то в настоящее время невозможно оптимизировать преобразование объектно-ориентированного кода в процедурный стиль или это очень сложно сделать?

Эта проблема не имеет ничего общего с ОО по сравнению с процедурным кодом, или один является более эффективным, чем другой. Основная проблема, которую вы наблюдаете здесь, заключается в том, что ostreams в C ++ требуют немного большей настройки и разрыва, а также имеют больший объем операций ввода-вывода, скоординированных с помощью встроенного кода, в то время как printf () имеет больше нестандартных выражений в предварительно скомпилированной библиотеке, поэтому не могу увидеть это в вашем маленьком листинге кода. На самом деле не ясно, что «лучше», и если у вас нет проблем с производительностью, связанных с профилированием, вы должны забыть об этом и сделать полезное программирование.

РЕДАКТИРОВАТЬ в ответ на комментарий:

Справедливый звонок - был немного грубоват - извините. Трудно провести различие на самом деле ... «только компилятор [знает] об объектах» верно в одном смысле - они не являются инкапсулированными, наполовину священными «вещами» для компилятора, какими они могут быть для программиста , И мы могли бы написать объект, который мог бы использоваться точно так же, как вы использовали cout, который исчезал бы во время компиляции и создавал код, эквивалентный версии printf (). Но cout и iostreams включают некоторую настройку, потому что это потокобезопасный и более встроенный, и обрабатывает различные локали, и это реальный объект с требованиями к хранилищу, потому что он несет в себе более независимую информацию о состоянии ошибки, хотите ли вы, чтобы исключения генерировались, конец -file условия (printf () влияет на "errno", который затем перекрывается следующим вызовом библиотеки / ОС) ....

Что может показаться более проницательным, так это сравнить, сколько дополнительного кода генерируется при печати еще одной строки, поскольку объем кода в основном равен некоторой постоянной нагрузке + некоторому количеству за использование, а в последнем отношении ostream - код на основе может быть как или более эффективным, чем printf (), в зависимости от запрашиваемых типов и форматирования. Также стоит отметить, что ...

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

... является правильным и более аналогичным вашему выражению printf () ... std::endl явно запрашивает ненужную операцию очистки, так как стандартная C ++ программа сбросит и закроет свои буферы, когда поток выходит из области видимости во всяком случае (там говорится, что сегодня есть интересный пост, где кажется, что чей-то компилятор Microsoft VisualC ++ не делает этого за них! - стоит присматривать, но в это трудно поверить).

2 голосов
/ 18 февраля 2011

Вы должны понимать, что в C ++ происходит много «других» вещей. Глобальные конструкторы, например. Также библиотеки разные.

объект потока C ++ намного сложнее, чем C io, и если вы посмотрите на ассемблер, вы увидите весь код pthreads в версии C ++.

Это не обязательно медленнее, но, безусловно, отличается.

...