Недостижимый код
В программировании и теории компиляторов, недостижимым кодом называют часть кода программы, которая ни при каких условиях не может быть исполнена, поскольку является недостижимой в графе потока управления[1][2].
Недостижимый код часто относят к одному из типов мёртвого кода, такая терминология обычно применяется при рассмотрении исходного кода программ[3][4]. Однако в теории компиляторов, эти понятия никак не связаны, мёртвым кодом там называют только достижимый, но не влияющий на вывод программы код[1][2][5].
Основные недостатки наличия в программе недостижимого кода:
- Занимает излишнюю память;
- Является причиной излишнего кэширования инструкций в кэш инструкций CPU — которое также снижает локальность данных;
- Затрудняет поддержку приложений — время и силы могут быть потрачены на поддержку и документирование части кода, которая является недостижимой, а значит никогда не исполняется.
Причины возникновения
Существование недостижимого кода может быть обусловлено разными факторами, например:
- Программные ошибки в сложных условных переходах;
- Вследствие внутренних преобразований, выполняемых оптимизирующим компилятором;
- Неполное тестирование новой или модифицированной программы, которому не удалось обнаружить недостижимый код;
- При исправлении одной ошибки, программист создал другую ошибку, которая обходит недостижимый код и не была обнаружена во время тестирования;
- Устаревший код, который не был полностью удалён программистом, так как он был смешан с действующим кодом;
- Устаревший код, который программист забыл удалить;
- Ранее полезный код, который никогда не будет исполнен, так как, в дальнейшем, ввод данных никогда не приведёт к исполнению этого кода;
- Устаревший код, который был намеренно сохранён, но сделан недостижимым, для того чтобы его можно было при необходимости снова включить в программу;
- Отладочные конструкции и остаточные части кода, которые ещё должны быть удалены из программы.
В последних пяти случаях, недостижимый код является унаследованным, то есть код, который был однажды полезным, но сейчас не используется.
Примеры
Рассмотрим следующий пример на языке Си:
int foo(int x, int y)
{
return x + y;
int z = x*y; /* Недостижимый код */
}
Операция int z = x*y
является недостижимым кодом, так как перед ней выполняется выход из процедуры (операции, стоящие после возврата из процедуры могут и не являться недостижимым кодом, например, если на метку, стоящую после возврата ссылается оператор goto).
Анализ
Поиск недостижимого кода в исходном коде может быть выполнен с помощью статического анализа кода[3][4]. В оптимизирующем компиляторе, выявить и удалить недостижимый код способна оптимизация удаления недостижимого кода, которая находит в графе потока управления (CFG) недостижимые узлы и удаляет их[6]. Простой анализ CFG-графа на недостижимые узлы часто бывает реализован в компиляторе в виде отдельной функции, т. н. сборщика мусора, которая вызывается сразу после преобразований, способных изменять граф потока управления[7].
Код может становиться недостижимым в результате выполнения других преобразований компилятора над промежуточным представлением, например оптимизации удаления общих подвыражений.
На практике, сложность реализуемого анализа существенно влияет на количество выявляемого недостижимого кода. Например, после свёртки констант и простого анализа потока управления можно обнаружить, что код внутри оператора if
в следующем примере является недостижимым:
int foo(void)
{
int n = 2 + 1;
if (n > 4)
{
printf("%d", n); /* Недостижимый код */
}
}
Однако, для того чтобы выявить недостижимый код в следующем примере, необходимо применить гораздо более сложный алгоритм анализа:
int foo(void)
{
double x = sqrt(2);
if (x > 4)
{
printf("%lf", x); /* Недостижимый код */
}
}
Одним из практических решений является подход, выполняющий сначала простой анализ выявления недостижимого кода, а затем использует профилировщик для обработки более сложных случаев. Профилировщик не может доказать, что какой-либо участок кода является недостижимым, но он может быть хорошей эвристикой для нахождения подозрительных узлов, которые, вероятно, являются недостижимыми. После обнаружения этих потенциально недостижимых узлов, можно применить какой-нибудь мощный алгоритм анализа недостижимого кода.
Примечания
- Engineering a Compiler — С. 544.
- Debray, S. K., Evans, W., Muth, R., and De Sutter, B. 2000. Compiler techniques for code compaction. ACM Trans. Program. Lang. Syst. 22, 2 (Mar. 2000), 378—415. (summary Архивировано)
- Dead code detection and removal . Aivosto. Дата обращения: 12 июля 2012. Архивировано 5 августа 2012 года.
- Compares some free alternatives to DCD (Dead Code Detector) (недоступная ссылка). Java.net. Дата обращения: 12 июля 2012. Архивировано 23 сентября 2012 года.
- Компиляторы — принципы, технологии, инструменты — С. 669 (недостижимый код), 713 (мёртвый код).
- Engineering a Compiler — С. 550.
- А. Ю. Дроздов, А. М. Степаненков. Управляемые пакеты оптимизаций. В Информационные технологии и вычислительные системы, 2004, № 3 (текст Архивировано)
Литература
- Cooper and Torczon. Engineering a Compiler. — Morgan Kaufmann, 2011. — С. 544-550, 593. — ISBN 978-0-12-088478-0.
- Альфред Ахо, Моника Лам, Рави Сети, Джеффри Ульман. Компиляторы: принципы, технологии и инструментарий = Compilers: Principles, Techniques, and Tools. — 2-е издание. — М.: «Вильямс», 2008. — 1184 с. — 1500 экз. — ISBN 978-5-8459-1349-4.
- Muchnick, Steven S. Advanced Compiler Design and Implementation. — Morgan Kaufmann Publishers, 1997. — С. 580-582. — ISBN 1-55860-320-4.