Como devo detectar arquivos #include desnecessários em um grande projeto C ++?

96

Estou trabalhando em um grande projeto C ++ no Visual Studio 2008 e há muitos arquivos com #includediretivas desnecessárias . Às vezes, os #includes são apenas artefatos e tudo irá compilar bem com eles removidos e, em outros casos, as classes podem ser declaradas e o #include pode ser movido para o .cpparquivo. Existem boas ferramentas para detectar esses dois casos?

caótico
fonte

Respostas:

50

Embora não revele arquivos de inclusão desnecessários, o Visual Studio tem uma configuração /showIncludes(clique com o botão direito em um .cpparquivo Properties->C/C++->Advanced) que produzirá uma árvore de todos os arquivos incluídos no momento da compilação. Isso pode ajudar a identificar arquivos que não deveriam ser incluídos.

Você também pode dar uma olhada no idioma pimpl para permitir que você escape com menos dependências de arquivo de cabeçalho para tornar mais fácil ver a sujeira que você pode remover.

Eclipse
fonte
1
/ showincludes é ótimo. Fazer isso manualmente era assustador sem isso.
caótico
30

PC Lint funciona muito bem para isso e também encontra todos os tipos de problemas idiotas para você. Ele tem opções de linha de comando que podem ser usadas para criar Ferramentas Externas no Visual Studio, mas descobri que o suplemento Visual Lint é mais fácil de trabalhar. Até a versão gratuita do Visual Lint ajuda. Mas dê uma chance ao PC-Lint. Configurá-lo de forma que não emita muitos avisos leva um pouco de tempo, mas você ficará surpreso com o que acontece.

Joe
fonte
3
Algumas instruções sobre como fazer isso com pc-lint podem ser encontradas em riverblade.co.uk/…
David Sykes
26

!!AVISO LEGAL!! Eu trabalho em uma ferramenta comercial de análise estática (não PC Lint). !!AVISO LEGAL!!

Existem vários problemas com uma abordagem simples de não análise:

1) Conjuntos de sobrecarga:

É possível que uma função sobrecarregada tenha declarações provenientes de arquivos diferentes. Pode ser que a remoção de um arquivo de cabeçalho resulte na escolha de uma sobrecarga diferente, em vez de um erro de compilação! O resultado será uma mudança silenciosa na semântica que pode ser muito difícil de rastrear depois.

2) Especializações de modelo:

Semelhante ao exemplo de sobrecarga, se você tiver especializações parciais ou explícitas para um modelo, deseja que todas elas fiquem visíveis quando o modelo for usado. Pode ser que as especializações para o modelo principal estejam em arquivos de cabeçalho diferentes. Remover o cabeçalho com a especialização não causará um erro de compilação, mas pode resultar em um comportamento indefinido se a especialização tivesse sido selecionada. (Veja: Visibilidade da especialização de template da função C ++ )

Conforme apontado por 'msalters', realizar uma análise completa do código também permite a análise do uso da classe. Ao verificar como uma classe é usada através de um caminho específico de arquivos, é possível que a definição da classe (e, portanto, todas as suas dependências) possa ser removida completamente ou pelo menos movida para um nível mais próximo da fonte principal no include árvore.

Richard Corden
fonte
@RichardCorden: Seu software (QA C ++) é muito caro.
Xander Tulip de
13
@XanderTulip: É difícil responder a isso sem acabar em um discurso de vendas - então, peço desculpas antecipadamente. IMHO, o que você deve considerar é quanto tempo levaria para um bom engenheiro encontrar coisas como esta (assim como muitos outros bugs de fluxo de linguagem / controle) em qualquer projeto de tamanho razoável. Conforme o software muda, a mesma tarefa precisa ser repetida várias vezes. Portanto, quando você calcula a quantidade de tempo economizado, o custo da ferramenta provavelmente não é significativo.
Richard Corden
10

Não conheço nenhuma dessas ferramentas e já pensei em escrever uma, mas descobri que esse é um problema difícil de resolver.

Digamos que seu arquivo de origem inclua ah e bh; ah contém #define USE_FEATURE_Xe bh usa #ifdef USE_FEATURE_X. Se #include "a.h"estiver comentado, seu arquivo ainda pode compilar, mas pode não fazer o que você espera. Detectar isso programaticamente não é trivial.

Qualquer ferramenta que faça isso também precisará conhecer seu ambiente de construção. Se ah se parece com:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Então, USE_FEATURE_Xsó é definido se WINNTfor definido, de modo que a ferramenta precisaria saber quais diretivas são geradas pelo próprio compilador, bem como quais são especificadas no comando de compilação em vez de em um arquivo de cabeçalho.

Graeme Perrow
fonte
9

Como Timmermans, não estou familiarizado com nenhuma ferramenta para isso. Mas conheço programadores que escreveram um script Perl (ou Python) para tentar comentar cada linha de inclusão, uma de cada vez, e depois compilar cada arquivo.


Parece que agora Eric Raymond tem uma ferramenta para isso .

O cpplint.py do Google tem uma regra "inclua o que você usa" (entre muitas outras), mas, pelo que posso dizer, não "inclua apenas o que você usa". Mesmo assim, pode ser útil.

Max Lybbert
fonte
Eu tive que rir quando li este. Meu chefe fez exatamente isso em um de nossos projetos no mês passado. Cabeçalho reduzido inclui alguns fatores.
Don Wakefield
2
codewarrior no mac costumava ter um script integrado para fazer isso, comentar, compilar, em caso de erro, remover o comentário, continuar até o final de #includes. Funcionou apenas para #includes no início de um arquivo, mas geralmente é onde eles estão. Não é perfeito, mas mantém as coisas razoavelmente sãs.
slycrel
5

Se você estiver interessado neste tópico em geral, pode dar uma olhada no design de software C ++ em grande escala de Lakos . É um pouco desatualizado, mas envolve muitos problemas de "design físico", como encontrar o mínimo absoluto de cabeçalhos que precisam ser incluídos. Eu realmente não vi esse tipo de coisa sendo discutido em nenhum outro lugar.

Adrian
fonte
4

Experimente o Include Manager . Ele se integra facilmente ao Visual Studio e visualiza seus caminhos de inclusão, o que ajuda a encontrar coisas desnecessárias. Internamente, ele usa o Graphviz, mas há muitos outros recursos interessantes. E embora seja um produto comercial tem um preço muito baixo.

Alex
fonte
3

Se seus arquivos de cabeçalho geralmente começam com

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(ao contrário de usar # pragma uma vez), você pode alterar para:

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

E uma vez que o compilador mostra o nome do arquivo cpp sendo compilado, isso permitiria que você soubesse pelo menos qual arquivo cpp está fazendo com que o cabeçalho seja trazido várias vezes.

Sam
fonte
12
Acho que não há problema em incluir cabeçalhos várias vezes. É bom incluir o que você usa e não depender dos arquivos de inclusão para fazer isso. Acho que o que o OP deseja é encontrar #includes que não sejam realmente usados.
Ryan Ginstrom
12
IMO ativamente errado a fazer. Os cabeçalhos devem incluir outros cabeçalhos quando não funcionariam sem eles. E quando você tem A.he B.hque ambos dependem C.he você incluir A.he B.h, porque você precisa de ambos, você vai incluir C.hduas vezes, mas isso é bom, porque o compilador irá ignorá-lo pela segunda vez e se você não fez, você tem que lembrar para sempre incluir C.hantes A.hou B.hterminar em inclusões muito mais inúteis.
Jan Hudec,
5
O conteúdo é preciso, é uma boa solução para localizar cabeçalhos incluídos várias vezes. No entanto, a pergunta original não foi respondida por isso e não posso imaginar quando isso seria uma boa ideia. Os arquivos Cpp devem incluir todos os cabeçalhos dos quais eles dependem, mesmo se o cabeçalho for incluído antes de algum outro lugar. Você não quer que seu projeto seja específico da ordem de compilação ou presuma que um cabeçalho diferente incluirá o que você precisa.
Jaypb
3

O PC-Lint pode realmente fazer isso. Uma maneira fácil de fazer isso é configurá-lo para detectar apenas arquivos de inclusão não utilizados e ignorar todos os outros problemas. Isso é bastante simples - para ativar apenas a mensagem 766 ("Arquivo de cabeçalho não usado no módulo"), apenas inclua as opções -w0 + e766 na linha de comando.

A mesma abordagem também pode ser usada com mensagens relacionadas, como 964 ("Arquivo de cabeçalho não usado diretamente no módulo") e 966 ("Arquivo de cabeçalho incluído indiretamente não usado no módulo").

FWIW Escrevi sobre isso com mais detalhes em uma postagem do blog na semana passada em http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


fonte
2

Se você está procurando remover #includearquivos desnecessários para diminuir os tempos de compilação, seu tempo e dinheiro podem ser melhor gastos paralelizando seu processo de compilação usando cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , etc.

Claro, se você já tem um processo de construção paralelo e ainda está tentando acelerá-lo, limpe suas #includediretivas e remova as dependências desnecessárias.

bk1e
fonte
2

Comece com cada arquivo de inclusão e certifique-se de que cada arquivo de inclusão inclui apenas o que é necessário para compilar a si mesmo. Todos os arquivos de inclusão que estão faltando para os arquivos C ++ podem ser adicionados aos próprios arquivos C ++.

Para cada arquivo de inclusão e fonte, comente cada arquivo de inclusão, um de cada vez, e veja se ele compila.

Também é uma boa ideia classificar os arquivos de inclusão em ordem alfabética e, onde isso não for possível, adicione um comentário.

Selwyn
fonte
2
Não tenho certeza de quão prático este comentário é, se um grande número de arquivos de implementação estiver envolvido.
Sonny,
1

Adicionar um ou ambos os #defines a seguir excluirá arquivos de cabeçalho frequentemente desnecessários e pode melhorar substancialmente os tempos de compilação, especialmente se o código não estiver usando funções da API do Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Veja http://support.microsoft.com/kb/166474

Roger Nelson
fonte
1
Não há necessidade de ambos - VC_EXTRALEAN define WIN32_LEAN_AND_MEAN
Aidan Ryan
1

Se ainda não o fez, usar um cabeçalho pré-compilado para incluir tudo o que você não vai alterar (cabeçalhos de plataforma, cabeçalhos de SDK externos ou partes estáticas já concluídas de seu projeto) fará uma grande diferença nos tempos de compilação.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Além disso, embora possa ser tarde demais para o seu projeto, organizá-lo em seções e não agrupar todos os cabeçalhos locais em um grande cabeçalho principal é uma boa prática, embora exija um pouco de trabalho extra.

anon6439
fonte
Ótima explicação sobre cabeçalhos pré-compilados: cygnus-software.com/papers/precompiledheaders.html (Não tenho certeza se a geração automática de cabeçalhos pré-compilados está quebrada nas versões recentes do VisualStudio, mas vale a pena verificar.)
idbrii
1

Se você trabalhar com o Eclipse CDT, experimente http://includator.com para otimizar sua estrutura de inclusão. No entanto, o Includator pode não saber o suficiente sobre as inclusões predefinidas do VC ++ e a configuração do CDT para usar o VC ++ com as inclusões corretas ainda não está incorporada ao CDT.

PeterSom
fonte
1

O IDE Jetbrains mais recente, CLion, mostra automaticamente (em cinza) os includes que não são usados ​​no arquivo atual.

Também é possível ter a lista de todos os includes não utilizados (e também funções, métodos, etc ...) do IDE.

Jean-Michaël Celerier
fonte
0

Algumas das respostas existentes afirmam que é difícil. Isso é realmente verdade, porque você precisa de um compilador completo para detectar os casos em que uma declaração de encaminhamento seria apropriada. Você não pode analisar C ++ sem saber o que os símbolos significam; a gramática é simplesmente ambígua demais para isso. Você deve saber se um determinado nome nomeia uma classe (pode ser declarada para frente) ou uma variável (não pode). Além disso, você precisa estar ciente do namespace.

MSalters
fonte
Você poderia apenas dizer "Decidir quais #includes são necessários equivale a resolver o problema da parada. Boa sorte :)" Claro, você pode usar heurísticas, mas não conheço nenhum software livre que faça isso.
porges
0

Se houver um cabeçalho específico que você acha que não é mais necessário (digamos string.h), você pode comentar esse include e colocá-lo abaixo de todos os includes:

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

É claro que os cabeçalhos de sua interface podem usar uma convenção #define diferente para registrar sua inclusão na memória do CPP. Ou nenhuma convenção; nesse caso, essa abordagem não funcionará.

Em seguida, reconstrua. Existem três possibilidades:

  • Ele constrói ok. string.h não era crítico de compilação e a inclusão para ele pode ser removida.

  • As viagens #error. string.g foi incluído indiretamente de alguma forma Você ainda não sabe se string.h é necessário. Se for necessário, você deve # incluí-lo diretamente (veja abaixo).

  • Você obtém algum outro erro de compilação. string.h era necessário e não está sendo incluído indiretamente, portanto, a inclusão estava correta para começar.

Observe que, dependendo da inclusão indireta quando seu .h ou .c usa diretamente outro .h, é quase certo que é um bug: você está prometendo que seu código só exigirá aquele cabeçalho, desde que algum outro cabeçalho que você esteja usando o exija, o que provavelmente não é o que você quis dizer.

As advertências mencionadas em outras respostas sobre cabeçalhos que modificam o comportamento em vez de declarar coisas que causam falhas de compilação também se aplicam aqui.

Britton Kerin
fonte