Pergunta

Estou trabalhando em um grande projeto C++ no Visual Studio 2008 e há muitos arquivos com arquivos desnecessários #include diretivas.Às vezes o #includes são apenas artefatos e tudo será compilado corretamente com eles removidos e, em outros casos, as classes podem ser declaradas adiante e o #include pode ser movido para o .cpp arquivo.Existem boas ferramentas para detectar esses dois casos?

Foi útil?

Solução

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 .cpp arquivo, Properties->C/C++->Advanced) que gerará uma árvore de todos os arquivos incluídos em tempo de 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ê tenha menos dependências de arquivo de cabeçalho para facilitar a visualização do lixo que você pode remover.

Outras dicas

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

Há uma nova ferramenta baseada em Clang, inclua o que você usa, que pretende fazer isso.

!!ISENÇÃO DE RESPONSABILIDADE!!Eu trabalho em uma ferramenta comercial de análise estática (não no PC Lint).!!ISENÇÃO DE RESPONSABILIDADE!!

Existem vários problemas com uma abordagem simples sem 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 detectar posteriormente.

2) Especializações de modelo:

Semelhante ao exemplo de sobrecarga, se você tiver especializações parciais ou explícitas para um modelo, você deseja que todas elas fiquem visíveis quando o modelo for usado.Pode ser que as especializações do modelo primário estejam em arquivos de cabeçalho diferentes.A remoção do cabeçalho com a especialização não causará um erro de compilação, mas poderá resultar em um comportamento indefinido se essa especialização tiver sido selecionada.(Ver: Visibilidade da especialização de modelo 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.

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

Digamos que seu arquivo de origem inclua a.h e b.h;a.h contém #define USE_FEATURE_X e b.h usa #ifdef USE_FEATURE_X.Se #include "a.h" estiver comentado, seu arquivo ainda poderá ser compilado, mas poderá não fazer o que você espera.Detectando isso programaticamente não é trivial.

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

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Então USE_FEATURE_X só é definido se WINNT está definido, portanto, a ferramenta precisaria saber quais diretivas são geradas pelo próprio compilador, bem como quais são especificadas no comando de compilação, e não em um arquivo de cabeçalho.

Assim como Timmermans, não estou familiarizado com nenhuma ferramenta para isso.Mas conheci 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.

do Google cpplint.py tem uma regra "inclua o que você usa" (entre muitas outras), mas até onde eu sei, não há "inclua apenas o que você usa." Mesmo assim, pode ser útil.

Se você estiver interessado neste tópico em geral, você pode querer dar uma olhada em Lakos' Design de software C++ em grande escala.É 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 discutida em nenhum outro lugar.

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

Você pode construir um gráfico de inclusão usando C/C++ Inclui Observador de Dependências de Arquivo, e encontre inclusões desnecessárias visualmente.

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

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

(em vez de usar #pragma uma vez), você pode mudar isso para:

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

E como o compilador gera o nome do arquivo cpp que está sendo compilado, isso permitirá que você saiba pelo menos qual arquivo cpp está fazendo com que o cabeçalho seja trazido várias vezes.

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"), basta incluir 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.

Se você deseja remover itens desnecessários #include arquivos para diminuir o tempo de construção, seu tempo e dinheiro podem ser melhor gastos paralelizando seu processo de construção usando cl.exe /MP, faça -j, Xoreax IncrediBuild, distcc/sorvete, etc.

Claro, se você já tem um processo de construção paralelo e ainda está tentando acelerá-lo, então limpe seu #include diretivas e remover essas dependências desnecessárias.

Comece com cada arquivo de inclusão e certifique-se de que cada arquivo de inclusão inclua apenas o que é necessário para compilar-se.Quaisquer arquivos de inclusão que estejam faltando nos arquivos C++ podem ser adicionados aos próprios arquivos C++.

Para cada arquivo de inclusão e de origem, comente cada arquivo de inclusão, um de cada vez, e veja se ele é compilado.

Também é uma boa ideia classificar os arquivos incluídos em ordem alfabética e, quando isso não for possível, adicionar um comentário.

A adição de um ou ambos os seguintes #Defines excluirá arquivos de cabeçalho muitas vezes desnecessários e poderão melhorar substancialmente os tempos de compilação, especialmente se o código que não estiver usando as funções da API do Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

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

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 SDK externos ou partes estáticas já concluídas do seu projeto) fará uma enorme diferença nos tempos de construçã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.

Se você trabalhasse com o Eclipse CDT, você poderia experimentar 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 inclusões corretas ainda não está incorporada no CDT.

O IDE Jetbrains mais recente, CLion, mostra automaticamente (em cinza) as inclusões que não são usadas no arquivo atual.

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

Algumas das respostas existentes afirmam que é difícil.Isso é verdade, porque você precisa de um compilador completo para detectar os casos em que uma declaração futura 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 posteriormente) ou uma variável (não pode).Além disso, você precisa estar ciente do namespace.

Talvez um pouco tarde, mas uma vez encontrei um script perl do WebKit que fazia exatamente o que você queria.Acredito que precisará de algumas adaptações (não sou muito versado em perl), mas deve funcionar:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(este é um branch antigo porque o trunk não possui mais o arquivo)

Se houver um cabeçalho específico que você acha que não é mais necessário (digamos String.h), você pode comentar que inclui e depois colocar isso abaixo de todos os inclui:

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

É claro que seus cabeçalhos de 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á.

Então reconstrua.Existem três possibilidades:

  • Ele constrói bem.String.h não foi compilado-crítico, e a inclusão pode ser removida.

  • O #error dispara.String.g foi incluído indiretamente de alguma forma você ainda não sabe se a string.h é necessária.Se for necessário, você deve diretamente #include (veja abaixo).

  • Você recebe algum outro erro de compilação.String.h foi necessário e não está sendo incluído indiretamente, então a inclusão estava correta para começar.

Observe que, dependendo da inclusão indireta, quando o seu .h ou .c usa diretamente outro .h é quase certamente um bug:Na verdade, você está prometendo que seu código exigirá apenas esse cabeçalho enquanto algum outro cabeçalho que você está usando exigir, 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 construção também se aplicam aqui.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top