Por que C e C++ são diferentes mesmo após a compilação?
-
15-11-2019 - |
Pergunta
Eu adivinhei, mas ainda fiquei surpreso ao ver que a saída desses dois programas, escritos em C e C++, quando compilados, eram muito diferentes.Isso me faz pensar que o conceito de objetos ainda existe mesmo no nível mais baixo.Isso adiciona sobrecarga?Em caso afirmativo, atualmente é uma otimização impossível converter código orientado a objetos em estilo processual ou apenas muito difícil de fazer?
olámundo.c
#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
olámundo.cpp
#include <iostream>
int main() {
std::cout << "Hello World!" << std::endl;
return 0;
}
Compilado assim:
gcc helloworld.cpp -o hwcpp.S -S -O2
gcc helloworld.c -o hwc.S -S -O2
Produzido este código:
Montagem 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
Montagem 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
Solução
Compiladores diferentes produzem códigos diferentes.Uma versão anterior do gcc versus a versão atual do gcc provavelmente produz um código diferente.
Mais importante, iostream
lida com muitas coisas stdio
não, então obviamente haverá alguma sobrecarga substancial.Entendo que, em teoria, eles poderiam ser compilados em código idêntico, mas o que eles estão fazendo não é tecnicamente idêntico.
Outras dicas
Seu problema aqui não é sobre objetos ou otimização:é isso printf
e cout
são feras fundamentalmente muito diferentes.Para uma comparação mais justa, substitua seu cout
instrução no código C++ com printf
.A otimização é um ponto discutível quando você está enviando para stdout, pois o gargalo certamente será o buffer do terminal.
Você não está chamando as mesmas funções no exemplo C++ que no exemplo C.Substitua os tubos std::cout pelo antigo printf, assim como o código C, e você verá uma correlação muito maior entre a saída dos dois compiladores.
Você precisa perceber que há muitas "outras" coisas acontecendo em C++.Construtores globais, por exemplo.Além disso, as bibliotecas são diferentes.
o objeto stream C++ é muito mais complicado que C io, e se você olhar através do assembler poderá ver todo o código para pthreads na versão C++.
Não é necessariamente mais lento, mas certamente é diferente.
Eu adivinhei, mas ainda fiquei surpreso ao ver que a saída desses dois programas, escritos em C e C++, quando compilados, eram muito diferentes.
Estou surpreso que você tenha ficado surpreso - são programas totalmente diferentes.
Isso me faz pensar que o conceito de objetos ainda existe mesmo no nível mais baixo.
Absolutamente...objetos são a forma como a memória é organizada e usada durante a execução do programa (sujeita a otimizações).
Isso adiciona sobrecarga?
Não necessariamente ou normalmente - os mesmos dados teriam que estar em algum lugar de qualquer maneira se o trabalho estivesse sendo coordenado da mesma maneira lógica.
Em caso afirmativo, atualmente é uma otimização impossível converter código orientado a objetos em estilo processual ou apenas muito difícil de fazer?
O problema não tem nada a ver com OO versus código processual, ou com um sendo mais eficiente que o outro.O principal problema que você observa aqui é que os ostreams do C++ exigem um pouco mais de configuração e desmontagem e têm mais E/S coordenada pelo código embutido, enquanto printf() tem mais fora de linha na biblioteca pré-compilada para que você não consigo ver isso na sua pequena listagem de código.Não está claro o que é "melhor" e, a menos que você tenha um problema de desempenho relacionado ao perfil dos programas, você deve esquecê-lo e fazer alguma programação útil.
EDITAR em resposta ao comentário:
Chamada justa - foi redigida um pouco duramente - desculpe.Na verdade, é uma distinção difícil de fazer..."somente o compilador [sabe] de objetos" é verdade em certo sentido - eles não são "coisas" discretas e encapsuladas para o compilador da maneira que podem ser para o programador.E poderíamos escrever um objeto que pudesse ser usado exatamente como você usou cout
isso desapareceria durante a compilação e produziria código equivalente à versão printf().Mas, cout
e iostreams envolve alguma configuração porque é thread-safe e mais embutido e lida com diferentes localidades, e é um objeto real com requisitos de armazenamento porque carrega informações mais independentes sobre o estado do erro, se você deseja lançar exceções, condições de fim de arquivo (printf () afeta "errno", que é então derrotado pela próxima chamada de biblioteca/sistema operacional)....
O que você pode achar mais esclarecedor é comparar quanto código extra é gerado quando você imprime mais uma string, já que a quantidade de código é basicamente uma sobrecarga constante + alguma quantidade por uso e, em último caso, ostream
O código baseado em - pode ser tão ou mais eficiente que printf(), dependendo dos tipos e formatação solicitados.Também é importante notar que...
std::cout << "Hello world!\n";
...está correto e mais análogo à sua instrução printf()... std::endl
solicita explicitamente uma operação de liberação desnecessária, já que um programa C++ compatível com o padrão irá liberar e fechar seus buffers conforme o fluxo sai do escopo de qualquer maneira (isto é, há uma postagem interessante hoje onde parece que o compilador Microsoft VisualC++ de alguém não está fazendo isso por eles!- vale a pena ficar de olho, mas é difícil de acreditar).