У меня есть устаревший код C ++, из которого я должен удалить неиспользуемый код. Проблема в том, что база кода большая.
Как я могу узнать, какой код никогда не вызывается / никогда не используется?
c++
optimization
dead-code
user63898
источник
источник
f()
, и вызовf()
однозначно преобразуется в 1-ю, то невозможно разрешить этот вызов до 2-й, просто добавив 3-ю названную функциюf()
- «худшее, что вы можете сделать» "добавив, что третья функция должна вызвать неоднозначность вызова и, следовательно, предотвратить компиляцию программы. Хотел бы (= в ужасе) увидеть контрпример.Ответы:
Существует два варианта неиспользуемого кода:
Для первого вида хороший компилятор может помочь:
-Wunused
(GCC, Clang ) должен предупреждать о неиспользуемых переменных, неиспользуемый анализатор Clang был даже увеличен, чтобы предупреждать о переменных, которые никогда не читаются (даже если используются).-Wunreachable-code
(старый GCC, удален в 2010 году ) должен предупреждать о локальных блоках, к которым никогда не осуществляется доступ (это происходит с ранним возвратом или условиями, которые всегда оцениваются как true)catch
блоках, потому что компилятор обычно не может доказать, что не будет выброшено исключениеДля второго вида это намного сложнее. Статически это требует анализа всей программы, и хотя оптимизация времени соединения может фактически удалить мертвый код, на практике программа настолько сильно трансформировалась во время выполнения, что практически невозможно передать значимую информацию пользователю.
Поэтому существует два подхода:
gcov
. Обратите внимание, что во время компиляции должны быть переданы определенные флаги, чтобы он работал правильно). Вы запускаете инструмент покрытия кода с хорошим набором различных входных данных (ваши юнит-тесты или нерегрессионные тесты), мертвый код обязательно находится в недоступном коде ... и поэтому вы можете начать отсюда.Если вы чрезвычайно заинтересованы в этой теме и у вас есть время и желание на самом деле разработать инструмент самостоятельно, я бы предложил использовать библиотеки Clang для создания такого инструмента.
Поскольку Clang проанализирует код для вас и выполнит разрешение перегрузки, вам не придется иметь дело с правилами языков C ++, и вы сможете сосредоточиться на рассматриваемой проблеме.
Однако этот метод не может идентифицировать виртуальные переопределения, которые не используются, так как они могут быть вызваны сторонним кодом, о котором вы не можете рассуждать.
источник
foo()
пометки как «вызываемого», когда оно появляется только в,if (0) { foo(); }
было бы бонусом, но требует дополнительных умов.)В случае неиспользуемых целых функций (и неиспользуемых глобальных переменных) GCC может фактически сделать большую часть работы за вас, при условии, что вы используете GCC и GNU ld.
При компиляции источника используйте
-ffunction-sections
и-fdata-sections
, затем при компоновке используйте-Wl,--gc-sections,--print-gc-sections
. Компоновщик теперь перечислит все функции, которые могут быть удалены, потому что они никогда не вызывались, и все глобальные переменные, на которые никогда не ссылались.(Конечно, вы также можете пропустить
--print-gc-sections
часть и позволить компоновщику удалить функции без уведомления, но оставьте их в источнике.)Примечание: это только найдет неиспользованные завершенные функции, оно не будет ничего делать с мертвым кодом внутри функций Функции, вызываемые из мертвого кода в живых функциях, также будут сохраняться.
Некоторые специфичные для C ++ функции также могут вызвать проблемы, в частности:
В обоих случаях все, что используется виртуальной функцией или конструктором глобальной переменной, также должно храниться.
Дополнительным предостережением является то, что если вы создаете общую библиотеку, настройки по умолчанию в GCC будут экспортировать каждую функцию в общей библиотеке, что приведет к ее «использованию» в отношении компоновщика. Чтобы это исправить, вам нужно установить по умолчанию скрытие символов вместо экспорта (используя, например
-fvisibility=hidden
), а затем явно выбрать экспортируемые функции, которые нужно экспортировать.источник
Хорошо, если вы используете g ++, вы можете использовать этот флаг
-Wunused
По документации:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Изменить : Вот другой полезный флаг
-Wunreachable-code
Согласно документации:Обновление : я нашел похожую тему Обнаружение мертвого кода в устаревшем проекте C / C ++
источник
-Wunused
предупреждает о переменных, которые объявлены (или объявлены и определены за один раз), но фактически никогда не используются. Кстати, это довольно раздражает охранников с ограниченным доступом: p В Clang есть экспериментальная реализация, которая также предупреждает о энергонезависимых переменных, которые записываются, но никогда не читаются (Тедом Кременеком).-Wunreachable-code
предупреждает о коде внутри функции , которая не может быть достигнута, это может быть код расположен послеthrow
илиreturn
заявления или коды в отрасли , который никогда не принимал (что случается в случае тавтологических сравнений), например.Я думаю, что вы ищете инструмент покрытия кода . Инструмент покрытия кода проанализирует ваш код во время его работы и даст вам знать, какие строки кода были выполнены и сколько раз, а какие - нет.
Вы можете попробовать дать этому инструменту покрытия с открытым исходным кодом шанс: TestCocoon - инструмент покрытия кода для C / C ++ и C #.
источник
void func()
в a.cpp, которая используется в b.cpp. Как компилятор может проверить, что func () используется в программе? Это работа линкеров.Реальный ответ здесь: вы никогда не можете знать наверняка.
По крайней мере, для нетривиальных случаев вы не можете быть уверены, что получили все это. Рассмотрим следующее из статьи Википедии о недоступном коде :
Как правильно отмечает Википедия, умный компилятор может поймать что-то подобное. Но рассмотрим модификацию:
Компилятор поймает это? Может быть. Но для этого нужно будет сделать больше, чем просто запустить
sqrt
скалярное значение. Придется выяснить, что(double)y
всегда будет целым числом (легко), а затем понять математический диапазонsqrt
для набора целых чисел (трудно). Очень сложный компилятор может сделать это дляsqrt
функции, или для каждой функции в math.h , или для любой функции с фиксированным вводом, чью область он может выяснить. Это становится очень, очень сложным, и сложность в основном безгранична. Вы можете продолжать добавлять уровни сложности к своему компилятору, но всегда будет способ проникнуть в некоторый код, который будет недоступен для любого данного набора входных данных.И затем есть наборы ввода, которые просто никогда не вводятся. Ввод, который не имеет смысла в реальной жизни или блокируется логикой проверки в другом месте. У компилятора нет возможности узнать о них.
Конечным результатом этого является то, что хотя программные инструменты, о которых упоминали другие, чрезвычайно полезны, вы никогда не узнаете наверняка, что все поймали, если потом не пройдете код вручную. Даже тогда вы никогда не будете уверены, что ничего не пропустили.
ИМХО, единственное реальное решение - это быть как можно бдительнее, использовать автоматизацию в вашем распоряжении, проводить рефакторинг, где вы можете, и постоянно искать способы улучшить свой код. Конечно, в любом случае это хорошая идея.
источник
Я не использовал его сам, но cppcheck утверждает, что нашел неиспользуемые функции. Это, вероятно, не решит полную проблему, но это может быть началом.
источник
cppcheck --enable=unusedFunction --language=c++ .
чтобы найти эти неиспользуемые функции.Вы можете попробовать использовать PC-lint / FlexeLint от Gimple Software . Утверждает, что
Я использовал это для статического анализа и нашел это очень хорошим, но я должен признать, что я не использовал это, чтобы определенно найти мертвый код.
источник
Мой обычный подход к поиску неиспользуемых вещей
watch "make 2>&1"
имеет тенденцию делать трюки на Unix.Это довольно длительный процесс, но он дает хорошие результаты.
источник
Отметьте как можно больше открытых функций и переменных как частные или защищенные, не вызывая ошибки компиляции, при этом попробуйте также выполнить рефакторинг кода. Сделав функции частными и в некоторой степени защищенными, вы сократили область поиска, поскольку частные функции можно вызывать только из одного и того же класса (если только нет глупых макросов или других хитростей, позволяющих обойти ограничение доступа, и если это так, я бы порекомендовал вам найти новую работу). Намного легче определить, что вам не нужна закрытая функция, поскольку только класс, над которым вы сейчас работаете, может вызывать эту функцию. Этот метод проще, если ваша кодовая база имеет небольшие классы и слабо связана. Если ваша кодовая база не имеет небольших классов или имеет очень тесную связь, я предлагаю сначала очистить их.
Далее следует отметить все оставшиеся общедоступные функции и составить граф вызовов, чтобы выяснить отношения между классами. Из этого дерева попытайтесь выяснить, какая часть ветви выглядит так, как будто ее можно обрезать.
Преимущество этого метода заключается в том, что вы можете делать это для каждого модуля, поэтому вы легко можете продолжить тестирование без большого периода времени, когда у вас сломана база кода.
источник
Если вы работаете в Linux, вам может понадобиться
callgrind
инструмент для анализа программ на C / C ++, который входит вvalgrind
комплект, который также содержит инструменты, которые проверяют утечки памяти и другие ошибки памяти (которые вы также должны использовать). Он анализирует работающий экземпляр вашей программы и выдает данные о графе вызовов и о затратах на производительность узлов в графе вызовов. Обычно он используется для анализа производительности, но также создает график вызовов для ваших приложений, чтобы вы могли видеть, какие функции вызываются, а также их вызывающих.Это, очевидно, дополняет статические методы, упомянутые в другом месте на странице, и это будет полезно только для удаления полностью неиспользуемых классов, методов и функций - это не поможет найти мертвый код внутри методов, которые фактически вызываются.
источник
Я действительно не использовал ни одного инструмента, который бы делал такие вещи ... Но, насколько я видел во всех ответах, никто никогда не говорил, что эта проблема неисчислима.
Что я имею в виду под этим? Эта проблема не может быть решена никаким алгоритмом на компьютере. Эта теорема (что такого алгоритма не существует) является следствием проблемы останова Тьюринга.
Все инструменты, которые вы будете использовать, являются не алгоритмами, а эвристикой (то есть не точными алгоритмами). Они не дадут вам точно весь код, который не используется.
источник
Одним из способов является использование отладчика и функции компилятора для устранения неиспользуемого машинного кода во время компиляции.
Как только некоторый машинный код будет удален, отладчик не позволит вам поставить точку останова на соответствующей строке исходного кода. Таким образом, вы ставите точки останова повсюду, запускаете программу и проверяете точки останова - те, которые находятся в состоянии «код не загружен для этого источника», соответствуют исключенному коду - либо этот код никогда не вызывается, либо он был встроен, и вам нужно выполнить какой-то минимум анализ, чтобы найти, какой из этих двух произошло.
По крайней мере, так работает в Visual Studio, и я думаю, что другие наборы инструментов также могут это делать.
Это много работы, но я думаю, быстрее, чем анализировать весь код вручную.
источник
CppDepend - это коммерческий инструмент, который может обнаруживать неиспользуемые типы, методы и поля и делать гораздо больше. Он доступен для Windows и Linux (но в настоящее время не имеет 64-битной поддержки) и поставляется с двухнедельной пробной версией.
Отказ от ответственности: я не работаю там, но у меня есть лицензия на этот инструмент (а также NDepend , который является более мощной альтернативой для кода .NET).
Для тех, кому интересно, вот пример встроенного (настраиваемого) правила для обнаружения мертвых методов, написанного на CQLinq :
источник
Это зависит от платформы, которую вы используете для создания приложения.
Например, если вы используете Visual Studio, вы можете использовать такой инструмент, как .NET ANTS Profiler, который может анализировать и профилировать ваш код. Таким образом, вы должны быстро узнать, какая часть вашего кода фактически используется. Eclipse также имеет эквивалентные плагины.
В противном случае, если вам нужно знать, какая функция вашего приложения фактически используется вашим конечным пользователем, и если вы можете легко выпустить приложение, вы можете использовать файл журнала для аудита.
Для каждой основной функции вы можете отследить ее использование, и через несколько дней / недель просто получите этот файл журнала и посмотрите на него.
источник
Я не думаю, что это можно сделать автоматически.
Даже с инструментами покрытия кода вы должны предоставить достаточные входные данные для запуска.
Может быть очень сложным и дорогостоящим инструментом статического анализа, таким как компилятор Coverity или LLVM .
Но я не уверен и предпочел бы ручную проверку кода.
ОБНОВЛЕНО
Ну .. только удаление неиспользуемых переменных, неиспользуемые функции не сложно, хотя.
ОБНОВЛЕНО
Прочитав другие ответы и комментарии, я твердо убежден, что это невозможно сделать.
Вы должны знать код, чтобы иметь значимую меру покрытия кода, и если вы знаете, что много ручного редактирования будет быстрее, чем подготовка / запуск / просмотр результатов покрытия.
источник
У меня был друг, который задавал мне этот вопрос сегодня, и я посмотрел вокруг на несколько многообещающих разработок Clang, например, ASTMatcher и Static Analyzer, которые могут иметь достаточную видимость в процессе компиляции для определения разделов мертвого кода, но затем я нашел это:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
Это в значительной степени полное описание того, как использовать несколько флагов GCC, которые, по-видимому, предназначены для идентификации символов, на которые нет ссылок!
источник
Общая проблема, если какая-то функция будет вызвана, является NP-Complete. Вы не можете заранее знать, будет ли вызвана какая-либо функция, поскольку вы не будете знать, остановится ли когда-нибудь машина Тьюринга. Вы можете получить, если есть некоторый (статически) путь, который идет от main () к функции, которую вы написали, но это не гарантирует, что она когда-либо будет вызвана.
источник
Хорошо, если вы используете g ++, вы можете использовать этот флаг -Wunused
По документации:
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Изменить: Вот другой полезный флаг -Wunreachable-код Согласно документации:
источник