Eu tenho código C ++ legado do qual devo remover o código não utilizado. O problema é que a base de código é grande.
Como posso descobrir qual código nunca é chamado / nunca usado?
c++
optimization
dead-code
user63898
fonte
fonte
f()
e uma chamada paraf()
resolver inequivocamente a 1ª, então não é possível fazer com que a chamada seja resolvida para a 2ª adicionando uma 3ª função chamadaf()
- a "pior coisa que você pode fazer "adicionando que a terceira função é fazer com que a chamada se torne ambígua e, portanto, impedir que o programa seja compilado. Adoraria (= ficar horrorizado) ver um contra-exemplo.Respostas:
Existem duas variedades de código não utilizado:
Para o primeiro tipo, um bom compilador pode ajudar:
-Wunused
(GCC, Clang ) deve avisar sobre variáveis não utilizadas, o analisador não utilizado da Clang foi incrementado para avisar sobre variáveis que nunca são lidas (mesmo sendo usadas).-Wunreachable-code
(GCC mais antigo, removido em 2010 ) deve avisar sobre blocos locais que nunca são acessados (isso acontece com retornos ou condições precoces que sempre avaliam como verdadeiros)catch
blocos não utilizados , porque o compilador geralmente não pode provar que nenhuma exceção será lançada.Para o segundo tipo, é muito mais difícil. Estaticamente, é necessária uma análise completa do programa e, embora a otimização do tempo do link possa realmente remover o código morto, na prática o programa foi tão transformado no momento em que é executado que é quase impossível transmitir informações significativas ao usuário.
Existem, portanto, duas abordagens:
gcov
. Observe que sinalizadores específicos devem ser passados durante a compilação para que funcione corretamente). Você executa a ferramenta de cobertura de código com um bom conjunto de entradas variadas (seus testes de unidade ou testes de não regressão), o código morto está necessariamente dentro do código não alcançado ... e assim você pode começar a partir daqui.Se você está extremamente interessado no assunto e tem tempo e inclinação para realmente elaborar uma ferramenta por si mesmo, sugiro usar as bibliotecas Clang para criar essa ferramenta.
Como o Clang analisará o código para você e executará a resolução de sobrecarga, você não precisará lidar com as regras das linguagens C ++ e poderá se concentrar no problema em questão.
No entanto, esse tipo de técnica não pode identificar as substituições virtuais que não são utilizadas, pois elas podem ser chamadas por código de terceiros que você não pode raciocinar.
fonte
foo()
de ser marcado como "chamada" quando ele aparece apenas emif (0) { foo(); }
seria um bônus, mas requer smarts extra.)No caso de funções inteiras não utilizadas (e variáveis globais não utilizadas), o GCC pode realmente fazer a maior parte do trabalho para você, desde que você esteja usando o GCC e o GNU ld.
Ao compilar a fonte, use
-ffunction-sections
e-fdata-sections
, em seguida, ao vincular o uso-Wl,--gc-sections,--print-gc-sections
. O vinculador agora listará todas as funções que podem ser removidas porque nunca foram chamadas e todas as globais que nunca foram referenciadas.(Obviamente, você também pode pular a
--print-gc-sections
peça e deixar o vinculador remover as funções silenciosamente, mas mantê-las na fonte.)Nota: isso encontrará apenas funções completas não utilizadas, não fará nada sobre código morto nas funções. As funções chamadas de código morto nas funções ativas também serão mantidas.
Alguns recursos específicos do C ++ também causarão problemas, em particular:
Nos dois casos, qualquer coisa usada por uma função virtual ou por um construtor de variável global também deve ser mantida.
Uma ressalva adicional é que, se você estiver criando uma biblioteca compartilhada, as configurações padrão no GCC exportarão todas as funções da biblioteca compartilhada, fazendo com que ela seja "usada" no que diz respeito ao vinculador. Para corrigir isso, você precisa definir o padrão para ocultar símbolos em vez de exportar (usando, por exemplo
-fvisibility=hidden
) e, em seguida, selecionar explicitamente as funções exportadas que você precisa exportar.fonte
Bem, se você estiver usando g ++, poderá usar esta flag
-Wunused
De acordo com a documentação:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Editar : Aqui está outro sinalizador útil
-Wunreachable-code
De acordo com a documentação:Atualização : Encontrei tópico semelhante Detecção de código morto no projeto C / C ++ herdado
fonte
-Wunused
avisa sobre variáveis que são declaradas (ou declaradas e definidas de uma só vez), mas que na verdade nunca são usadas. A propósito, é bastante irritante para os guardas com escopo definido: p Existe uma implementação experimental em Clang que também alerta para variáveis não voláteis que são escritas para, mas nunca lidas (por Ted Kremenek).-Wunreachable-code
adverte sobre o código dentro de uma função que não pode ser alcançado, pode ser código localizado após umathrow
oureturn
declaração ou código em um ramo que nunca é levado (o que acontece em caso de comparações tautológicos), por exemplo.Eu acho que você está procurando uma cobertura de código ferramenta de . Uma ferramenta de cobertura de código analisará seu código durante a execução e informará quais linhas de código foram executadas e quantas vezes, além de quais não foram.
Você pode tentar dar a essa ferramenta de cobertura de código-fonte aberto uma chance: TestCocoon - ferramenta de cobertura de código para C / C ++ e C #.
fonte
void func()
em a.cpp, que é usado em b.cpp. Como o compilador pode verificar, se o func () é usado no programa? É trabalho de vinculadores.A resposta real aqui é: você nunca pode realmente ter certeza.
Pelo menos, para casos não triviais, você não pode ter certeza de ter conseguido tudo isso. Considere o seguinte no artigo da Wikipedia sobre código inacessível :
Como a Wikipedia observa corretamente, um compilador inteligente pode ser capaz de capturar algo assim. Mas considere uma modificação:
O compilador vai entender isso? Talvez. Mas, para fazer isso, será necessário fazer mais do que executar
sqrt
contra um valor escalar constante. Ele terá que descobrir que(double)y
sempre será um número inteiro (fácil) e, em seguida, entender o intervalo matemático desqrt
para o conjunto de números inteiros (difícil). Um compilador muito sofisticado pode fazer isso para asqrt
função, ou para todas as funções em math.h , ou para qualquer função de entrada fixa cujo domínio ele possa descobrir. Isso fica muito, muito complexo, e a complexidade é basicamente ilimitada. Você pode continuar adicionando camadas de sofisticação ao seu compilador, mas sempre haverá uma maneira de ocultar algum código que será inacessível para qualquer conjunto de entradas.E existem os conjuntos de entrada que simplesmente nunca são inseridos. Entrada que não faria sentido na vida real ou seria bloqueada pela lógica de validação em outro lugar. Não há como o compilador saber sobre isso.
O resultado final disso é que, embora as ferramentas de software mencionadas por outros sejam extremamente úteis, você nunca saberá com certeza que capturou tudo, a menos que repasse o código manualmente posteriormente. Mesmo assim, você nunca terá certeza de que não perdeu nada.
A única solução real, IMHO, é ser o mais vigilante possível, usar a automação à sua disposição, refatorar onde puder e procurar constantemente maneiras de melhorar seu código. Claro, é uma boa ideia fazer isso de qualquer maneira.
fonte
Eu não o usei, mas o cppcheck afirma encontrar funções não utilizadas. Provavelmente não resolverá o problema completo, mas pode ser um começo.
fonte
cppcheck --enable=unusedFunction --language=c++ .
para encontrar essas funções não utilizadas.Você pode tentar usar o PC-lint / FlexeLint da Gimple Software . Alega
Usei-o para análise estática e achei muito bom, mas tenho que admitir que não o usei para encontrar especificamente código morto.
fonte
Minha abordagem normal para encontrar coisas não utilizadas é
watch "make 2>&1"
tende a fazer o truque no Unix.Este é um processo um tanto demorado, mas oferece bons resultados.
fonte
Marque tantas funções públicas e variáveis como privadas ou protegidas sem causar erro de compilação; ao fazer isso, tente refatorar o código. Ao tornar as funções privadas e, até certo ponto, protegidas, você reduziu sua área de pesquisa, pois as funções privadas só podem ser chamadas da mesma classe (a menos que haja macro estúpida ou outros truques para contornar a restrição de acesso, e se for esse o caso, recomendo encontrar um novo emprego). É muito mais fácil determinar que você não precisa de uma função privada, pois somente a classe em que você está trabalhando no momento pode chamar essa função. Esse método é mais fácil se a sua base de código tiver classes pequenas e for fracamente acoplada. Se sua base de código não possui classes pequenas ou possui um acoplamento muito rígido, sugiro limpá-las primeiro.
A seguir, marque todas as demais funções públicas e faça um gráfico de chamada para descobrir o relacionamento entre as classes. Nesta árvore, tente descobrir qual parte do ramo parece que pode ser aparada.
A vantagem desse método é que você pode fazê-lo por módulo, por isso é fácil continuar passando o seu melhor, sem ter um grande período de tempo quando você tem uma base de código quebrada.
fonte
Se você estiver no Linux, convém procurar
callgrind
uma ferramenta de análise de programa C / C ++ que faça parte dovalgrind
conjunto, que também contém ferramentas que verificam vazamentos de memória e outros erros de memória (que você também deve usar). Ele analisa uma instância em execução do seu programa e produz dados sobre o gráfico de chamadas e os custos de desempenho dos nós no gráfico de chamadas. Geralmente é usado para análise de desempenho, mas também produz um gráfico de chamadas para seus aplicativos, para que você possa ver quais funções são chamadas, bem como seus chamadores.Obviamente, isso é complementar aos métodos estáticos mencionados em outras partes da página e só será útil para eliminar classes, métodos e funções totalmente não utilizados - não ajuda a encontrar o código morto dentro dos métodos que são realmente chamados.
fonte
Eu realmente não usei nenhuma ferramenta que faça isso ... Mas, até onde eu vi em todas as respostas, ninguém nunca disse que esse problema é incontestável.
O que quero dizer com isso? Que esse problema não pode ser resolvido por nenhum algoritmo de um computador. Esse teorema (de que tal algoritmo não existe) é um corolário do Problema de Parada de Turing.
Todas as ferramentas que você usará não são algoritmos, mas heurísticas (ou seja, algoritmos não exatos). Eles não fornecerão exatamente todo o código que não é usado.
fonte
Uma maneira é usar um depurador e o recurso de compilador para eliminar o código de máquina não utilizado durante a compilação.
Depois que algum código de máquina é eliminado, o depurador não permite que você coloque um breakpojnt na linha correspondente do código-fonte. Então você coloca pontos de interrupção em todos os lugares, inicia o programa e inspeciona os pontos de interrupção - aqueles que estão no estado "nenhum código carregado para esta fonte" correspondem ao código eliminado - ou esse código nunca é chamado ou foi incorporado e é necessário executar um mínimo análise para descobrir qual desses dois aconteceu.
Pelo menos é assim que funciona no Visual Studio e acho que outros conjuntos de ferramentas também podem fazer isso.
Isso dá muito trabalho, mas acho que mais rápido do que analisar manualmente todo o código.
fonte
O CppDepend é uma ferramenta comercial que pode detectar tipos, métodos e campos não utilizados e fazer muito mais. Está disponível para Windows e Linux (mas atualmente não possui suporte a 64 bits) e vem com uma avaliação de 2 semanas.
Isenção de responsabilidade: não trabalho lá, mas possuo uma licença para esta ferramenta (assim como o NDepend , que é uma alternativa mais poderosa ao código .NET).
Para quem está curioso, aqui está um exemplo de regra interna (personalizável) para detectar métodos mortos, escrita em CQLinq :
fonte
Depende da plataforma que você usa para criar seu aplicativo.
Por exemplo, se você usa o Visual Studio, pode usar uma ferramenta como o .NET ANTS Profiler, capaz de analisar e criar um perfil do seu código. Dessa forma, você deve saber rapidamente qual parte do seu código é realmente usada. O Eclipse também possui plugins equivalentes.
Caso contrário, se você precisar saber qual função do seu aplicativo é realmente usada pelo usuário final e se puder liberá-lo facilmente, poderá usar um arquivo de log para uma auditoria.
Para cada função principal, você pode rastrear seu uso e, após alguns dias / semana, obtenha esse arquivo de log e dê uma olhada nele.
fonte
Eu não acho que isso possa ser feito automaticamente.
Mesmo com as ferramentas de cobertura de código, você precisa fornecer dados de entrada suficientes para executar.
Pode ser uma ferramenta de análise estática muito complexa e de alto preço, como os compiladores Coverity ou LLVM, que podem ajudar.
Mas não tenho certeza e prefiro a revisão manual do código.
ATUALIZADA
Bem .. apenas removendo variáveis não utilizadas, funções não utilizadas não é difícil.
ATUALIZADA
Depois de ler outras respostas e comentários, estou mais convencido de que isso não pode ser feito.
Você precisa conhecer o código para obter uma medida significativa da cobertura do código e, se souber que muita edição manual será mais rápida do que preparar / executar / revisar os resultados da cobertura.
fonte
Hoje, um amigo me fez essa pergunta e observei alguns desenvolvimentos promissores do Clang, como o ASTMatcher e o Static Analyzer que podem ter visibilidade suficiente durante a compilação para determinar as seções do código morto, mas então eu encontrou o seguinte:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
É praticamente uma descrição completa de como usar alguns sinalizadores do GCC que aparentemente foram projetados com a finalidade de identificar símbolos não referenciados!
fonte
O problema geral de se alguma função será chamada é NP-Complete. Você não pode saber antecipadamente de uma maneira geral se alguma função será chamada, pois você não saberá se uma máquina de Turing irá parar. Você pode descobrir se existe algum caminho (estaticamente) que vai de main () para a função que você escreveu, mas isso não garante que será chamado.
fonte
Bem, se você estiver usando g ++, poderá usar esta flag -Wunused
De acordo com a documentação:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Edit: Aqui está outro sinalizador útil -Wunreachable-code De acordo com a documentação:
fonte