Por que a alocação inicial de C ++ é muito maior que a de C?

138

Ao usar o mesmo código, simplesmente alterar o compilador (de um compilador C para um compilador C ++) alterará a quantidade de memória alocada. Não tenho muita certeza do porquê disso e gostaria de entender mais. Até agora, a melhor resposta que recebi é "provavelmente os fluxos de E / S", o que não é muito descritivo e me faz pensar sobre o aspecto do C ++ como "você não paga pelo que não usa".

Estou usando os compiladores Clang e GCC, versões 7.0.1-8 e 8.3.0-6, respectivamente. Meu sistema está sendo executado no Debian 10 (Buster), mais recente. Os benchmarks são feitos via Valgrind Massif.

#include <stdio.h>

int main() {
    printf("Hello, world!\n");
    return 0;
}

O código usado não muda, mas, se eu compilar como C ou C ++, ele altera os resultados do benchmark Valgrind. Os valores permanecem consistentes entre os compiladores, no entanto. As alocações de tempo de execução (pico) para o programa são as seguintes:

  • GCC (C): 1.032 bytes (1 KB)
  • G ++ (C ++): 73.744 bytes, (~ 74 KB)
  • Clang (C): 1.032 bytes (1 KB)
  • Clang ++ (C ++): 73.744 bytes (~ 74 KB)

Para compilar, eu uso os seguintes comandos:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

Para o Valgrind, eu corro valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langem cada compilador e idioma, depois ms_printpara exibir os picos.

Estou fazendo algo errado aqui?

Rerumu
fonte
11
Para começar, como você está construindo? Quais opções você usa? E como você mede? Como você executa o Valgrind?
Algum programador,
17
Se bem me lembro, os compiladores C ++ modernos têm que ter um modelo de exceção em que não há desempenho atingido ao inserir um trybloco às custas de uma maior quantidade de memória, talvez com uma tabela de salto ou algo assim. Talvez tente compilar sem exceções e ver qual o impacto que isso tem. Edit: De fato, iterativamente, tente desabilitar vários recursos do c ++ para ver qual o impacto que isso tem sobre o espaço ocupado pela memória.
François Andrieux
3
Ao compilar com clang++ -xcem vez de clang, a mesma alocação estava lá, o que sugere fortemente a sua função bibliotecas vinculadas
Justin
14
@bigwillydos Este é realmente o C ++, não vejo nenhuma parte das especificações do C ++ que ele quebra ... Além de incluir o stdio.h em vez do cstdio, mas isso é permitido pelo menos na versão mais antiga do C ++. O que você acha que está "malformado" neste programa?
Vality
4
Acho suspeito que esses compiladores gcc e clang gerem exatamente o mesmo número de bytes no Cmodo e exatamente o mesmo número de bytes C++. Você cometeu um erro de transcrição?
RonJohn

Respostas:

149

O uso de heap vem da biblioteca padrão C ++. Ele aloca memória para uso da biblioteca interna na inicialização. Se você não o vincular, deve haver zero diferença entre as versões C e C ++. Com o GCC e o Clang, você pode compilar o arquivo com:

g ++ -Wl, - conforme necessário main.cpp

Isso instruirá o vinculador a não vincular contra bibliotecas não utilizadas. No seu código de exemplo, a biblioteca C ++ não é usada, portanto, não deve ser vinculada à biblioteca padrão C ++.

Você também pode testar isso com o arquivo C. Se você compilar com:

gcc main.c -lstdc ++

O uso do heap reaparecerá, mesmo que você tenha criado um programa em C.

O uso de heap é obviamente dependente da implementação específica da biblioteca C ++ que você está usando. No seu caso, essa é a biblioteca GNU C ++, libstdc ++ . Outras implementações podem não alocar a mesma quantidade de memória ou podem não alocar nenhuma memória (pelo menos não na inicialização). A biblioteca LLVM C ++ ( libc ++ ), por exemplo, não faz alocação de heap na inicialização, pelo menos no meu Linux máquina:

clang ++ -stdlib = libc ++ main.cpp

O uso de heap é o mesmo que não vincular nada a ele.

(Se a compilação falhar, o libc ++ provavelmente não está instalado. O nome do pacote geralmente contém "libc ++" ou "libcxx".)

Nikos C.
fonte
50
Ao ver esta resposta, meu primeiro pensamento é: " Se esse sinalizador ajuda a reduzir a sobrecarga desnecessária, por que não está ativado por padrão? ". Existe uma boa resposta para isso?
Nat
4
@ Nat Meu palpite é que no momento do desenvolvimento é mais lento para compilar. Quando você estiver pronto para criar uma versão, você deve ativá-la. Também em uma base de código normal / grande, a diferença pode ser mínima (se você estiver usando muitas bibliotecas STD, etc.)
DarcyThomas
24
@ Nat O -Wl,--as-neededsinalizador remove as bibliotecas especificadas nos -lsinalizadores, mas você não está realmente usando. Portanto, se você não usa uma biblioteca, simplesmente não vincule-a. Você não precisa desse sinalizador para isso. No entanto, se seu sistema de compilação adicionar muitas bibliotecas e seria muito trabalhoso limpá-las e vincular apenas as necessárias, você poderá usar esse sinalizador. A biblioteca padrão é uma exceção, pois é vinculada automaticamente. Portanto, este é um caso de esquina.
Nikos C.
36
O @Nat - conforme necessário pode ter efeitos colaterais indesejados, funciona verificando se você usa algum símbolo de uma biblioteca e expulsa aqueles que falharam no teste. MAS: uma biblioteca também pode fazer várias coisas implicitamente, por exemplo, se você tiver uma instância estática de C ++ na biblioteca, seu construtor será automaticamente chamado. Existem casos raros em que uma biblioteca para a qual você não chama explicitamente é necessária, mas elas existem.
Norbert Lange
3
@NikosC. Os sistemas de compilação não sabem automaticamente quais símbolos seu aplicativo usa e quais bibliotecas os implementam (varia entre compiladores, arcos, distribuições e bibliotecas c / c ++). Conseguir isso certo é bastante problemático, pelo menos para as bibliotecas de tempo de execução base. Mas, para os raros casos em que você precisa de uma biblioteca, você deve simplesmente usar - não conforme necessário e deixar - como necessário em qualquer outro lugar. Um exemplo de usuário que eu vi são bibliotecas para rastreamento / depuração (lttng) e bibliotecas que fazem algo do tipo de autenticação / conexão.
Norbert Lange
16

Nem o GCC nem o Clang são compiladores - na verdade, são programas de driver de cadeia de ferramentas. Isso significa que eles invocam o compilador, o montador e o vinculador.

Se você compilar seu código com um compilador C ou C ++, obterá o mesmo assembly produzido. O Assembler produzirá os mesmos objetos. A diferença é que o driver da cadeia de ferramentas fornecerá uma entrada diferente para o vinculador para os dois idiomas diferentes: inicializações diferentes (o C ++ requer código para executar construtores e destruidores para objetos com duração de armazenamento estático ou local de encadeamento no nível do namespace e requer infraestrutura para a pilha quadros para suportar o desenrolamento durante o processamento de exceção, por exemplo), a biblioteca padrão C ++ (que também possui objetos com duração de armazenamento estático no nível do espaço para nome) e provavelmente bibliotecas de tempo de execução adicionais (por exemplo, libgcc com sua infraestrutura de desenrolamento de pilha).

Em suma, não é o compilador que está causando o aumento da área ocupada, é a vinculação de itens que você escolheu usar escolhendo a linguagem C ++.

É verdade que o C ++ tem a filosofia "pague apenas pelo que você usa", mas usando a linguagem, você paga. Você pode desativar partes da linguagem (RTTI, manipulação de exceções), mas não usa mais o C ++. Como mencionado em outra resposta, se você não usar a biblioteca padrão, poderá instruir o driver a deixar isso de fora (--Wl, - conforme necessário), mas se você não usar nenhum dos recursos do C ++ ou de sua biblioteca, por que você está escolhendo o C ++ como linguagem de programação?

Stephen M. Webb
fonte
O fato de habilitar o tratamento de exceções tem um custo, mesmo que você não o utilize realmente, é um problema. Isso não é normal para os recursos do C ++ em geral, e é algo que os grupos de trabalho dos padrões do C ++ estão tentando pensar em maneiras de corrigir. Veja a palestra de Herb Sutter em ACCU 2019 Desm fragmentando C ++: Tornando as exceções mais acessíveis e utilizáveis . É um fato infeliz no C ++ atual. E as exceções tradicionais do C ++ provavelmente sempre terão esse custo, mesmo se / quando novos mecanismos para novas exceções forem adicionados com uma palavra-chave.
Peter Cordes