Компиляторы, которые я использовал в C или Java, имеют предотвращение мертвого кода (предупреждение, когда строка никогда не будет выполнена). Мой профессор говорит, что эта проблема никогда не может быть полностью решена компиляторами. Мне было интересно, почему это так. Я не слишком знаком с фактическим кодированием компиляторов, так как это теоретический класс. Но мне было интересно, что они проверяют (например, возможные строки ввода против допустимых входных данных и т. Д.), И почему этого недостаточно.
compiler-theory
ученик
источник
источник
if (isPrime(1234234234332232323423)){callSomething();}
этот код когда-нибудь будет вызывать или нет? Есть много других примеров, когда решение о том, вызывать ли функцию когда-либо, намного дороже, чем просто включить ее в программу.public static void main(String[] args) {int counterexample = findCollatzConjectureCounterexample(); System.out.println(counterexample);}
<- является ли println вызов мертвым кодом? Даже люди не могут решить это!Ответы:
Проблема мертвого кода связана с проблемой остановки .
Алан Тьюринг доказал, что невозможно написать общий алгоритм, который будет задан программе и сможет решить, останавливается ли эта программа для всех входов. Вы можете написать такой алгоритм для определенных типов программ, но не для всех программ.
Как это связано с мертвым кодом?
Проблема остановки может быть сведена к проблеме поиска мертвого кода. То есть, если вы найдете алгоритм, который может обнаружить мертвый код в любой программе, вы можете использовать этот алгоритм, чтобы проверить, остановится ли какая-либо программа. Поскольку это оказалось невозможным, из этого следует, что написание алгоритма для мертвого кода также невозможно.
Как вы переводите алгоритм для мертвого кода в алгоритм для проблемы остановки?
Все просто: вы добавляете строку кода после завершения программы, которую хотите проверить на остановку. Если ваш детектор мертвого кода обнаружит, что эта строка не работает, то вы знаете, что программа не останавливается. Если это не так, то вы знаете, что ваша программа останавливается (переходит к последней строке, а затем к добавленной строке кода).
Компиляторы обычно проверяют наличие вещей, которые могут быть проверены во время компиляции как мертвые. Например, блоки, которые зависят от условий, которые могут быть определены как ложные во время компиляции. Или любое утверждение после
return
(в той же области).Это конкретные случаи, и поэтому для них можно написать алгоритм. Может быть возможно написать алгоритмы для более сложных случаев (например, алгоритм, который проверяет, является ли условие синтаксически противоречием и поэтому всегда будет возвращать false), но, тем не менее, он не будет охватывать все возможные случаи.
источник
256^(2^64)
состояний таковO(1)
, что обнаружение мертвого кода может быть сделано за полиномиальное время.Что ж, давайте возьмем классическое доказательство неразрешимости проблемы остановки и изменим детектор остановки на детектор мертвого кода!
Программа на C #
Если
YourVendor.Compiler.HasDeadCode(quine_text)
возвращаетсяfalse
, то линияSystem.Console.WriteLn("Dead code!");
не будет когда - либо выполнена, так что эта программа на самом деле это есть мертвый код, а детектор был неправ.Но если он вернется
true
, то строкаSystem.Console.WriteLn("Dead code!");
будет выполнена, и, поскольку в программе больше нет кода, мертвого кода вообще нет, поэтому снова, детектор был не прав.Таким образом, у вас есть это, детектор мертвого кода, который возвращает только «Есть мертвый код» или «Нет мертвого кода», может иногда давать неправильные ответы.
источник
Если проблема остановки слишком неясна, подумайте об этом следующим образом.
Возьмите математическую задачу, которая, как полагают, верна для всех n положительных чисел , но не доказана, чтобы быть верной для каждого n . Хорошим примером может служить гипотеза Гольдбаха о том , что любое положительное целое число, большее двух, может быть представлено суммой двух простых чисел. Затем (с соответствующей библиотекой bigint) запустите эту программу (псевдокод следует):
Реализация
isGoldbachsConjectureTrueFor()
оставлена в качестве упражнения для читателя, но для этой цели может быть простая итерация по всем простым числам меньше, чемn
Теперь, по логике, вышеприведенное должно быть эквивалентно:
(т.е. бесконечный цикл) или
так как гипотеза Гольдбаха должна быть либо верной, либо неверной. Если бы компилятор всегда мог устранить мертвый код, то в любом случае здесь обязательно был бы мертвый код. Однако при этом, по крайней мере, ваш компилятор должен будет решать сколь-нибудь сложные задачи. Мы могли бы обеспечить проблемы доказуемо трудно , что бы решить (например , NP-полных задач) , чтобы определить , какой бит кода устранить. Например, если мы возьмем эту программу:
мы знаем, что программа распечатает «Найденное значение SHA» или «Не найденное значение SHA» (бонусные баллы, если вы можете сказать мне, какое из них истинно). Однако, чтобы компилятор мог разумно оптимизировать процесс, он бы занимал порядка 2 ^ 2048 итераций. На самом деле это была бы отличная оптимизация, так как я предсказываю, что вышеуказанная программа будет (или может) работать до тепловой смерти вселенной, а не печатать что-либо без оптимизации.
источник
sha256
возвращает байтовый массив, а байтовые массивы не сравниваются равными строкам на вашем языке.Implementation of isGoldbachsConjectureTrueFor() is left as an exercise for the reader
Это заставило меня посмеяться.Я не знаю, есть ли в C ++ или Java
Eval
функция типа, но многие языки позволяют вызывать методы по имени . Рассмотрим следующий (надуманный) пример VBA.Имя вызываемого метода невозможно узнать до времени выполнения. Следовательно, по определению компилятор не может знать с абсолютной уверенностью, что конкретный метод никогда не вызывается.
На самом деле, учитывая пример вызова метода по имени, логика ветвления даже не нужна. Просто говоря
Больше, чем может определить компилятор. Когда код компилируется, все, что знает компилятор, это то, что определенное строковое значение передается этому методу. Он не проверяет, существует ли этот метод до времени выполнения. Если метод не вызывается в другом месте с помощью более обычных методов, попытка найти мертвые методы может вернуть ложные срабатывания. Та же проблема существует в любом языке, который позволяет вызывать код через отражение.
источник
Безусловный мертвый код может быть обнаружен и удален продвинутыми компиляторами.
Но есть и условный мертвый код. Это код, который не может быть известен во время компиляции и может быть обнаружен только во время выполнения. Например, программное обеспечение может быть конфигурируемым, чтобы включать или исключать определенные функции в зависимости от предпочтений пользователя, делая определенные части кода кажущимися мертвыми в определенных сценариях. Это не настоящий мертвый код.
Существуют специальные инструменты, которые могут выполнять тестирование, разрешать зависимости, удалять условный мертвый код и рекомбинировать полезный код во время выполнения для эффективности. Это называется динамическим устранением мертвого кода. Но, как вы можете видеть, это выходит за рамки компиляторов.
источник
Простой пример:
Теперь предположим, что порт 0x100 предназначен для возврата только 0 или 1. В этом случае компилятор не может понять, что
else
блок никогда не будет выполнен.Однако в этом базовом примере:
Здесь компилятор может вычислить, является ли
else
блок мертвым кодом. Таким образом, компилятор может предупреждать о мертвом коде, только если у него достаточно данных, чтобы выяснить мертвый код, а также он должен знать, как применить эти данные, чтобы выяснить, является ли данный блок мертвым кодом.РЕДАКТИРОВАТЬ
Иногда данные просто недоступны во время компиляции:
При компиляции a.cpp компилятор не может знать, что
boolMethod
всегда возвращаетсяtrue
.источник
Компилятору всегда будет не хватать некоторой контекстной информации. Например, вы можете знать, что двойное значение никогда не превышает 2, потому что это особенность математической функции, которую вы используете из библиотеки. Компилятор даже не видит код в библиотеке, и он никогда не узнает всех возможностей всех математических функций и не обнаружит все сложные и сложные способы их реализации.
источник
Компилятор не обязательно видит всю программу. Я мог бы иметь программу, которая вызывает общую библиотеку, которая вызывает функцию в моей программе, которая не вызывается напрямую.
Таким образом, функция, которая не работает с библиотекой, с которой она скомпилирована, может стать живой, если эта библиотека была изменена во время выполнения.
источник
Если компилятор может точно удалить весь мертвый код, он будет называться интерпретатором .
Рассмотрим этот простой сценарий:
my_func()
может содержать произвольный код, и для того, чтобы компилятор мог определить, вернет ли он значение true или false, ему придется либо выполнить код, либо сделать что-то, что функционально эквивалентно выполнению кода.Идея компилятора заключается в том, что он выполняет только частичный анализ кода, тем самым упрощая работу отдельной работающей среды. Если вы выполните полный анализ, это уже не компилятор.
Если вы рассматриваете компилятор как функцию
c()
, гдеc(source)=compiled code
, и работающую среду какr()
, гдеr(compiled code)=program output
, то для определения вывода для любого исходного кода вы должны вычислить значениеr(c(source code))
. Если для вычисленияc()
требуется знание значенияr(c())
для любого входа, нет необходимости в отдельномr()
иc()
: вы можете просто извлечь функциюi()
изc()
такого, чтоi(source)=program output
.источник
Другие прокомментировали проблему остановки и так далее. Они обычно применяются к частям функций. Однако может быть трудно / невозможно узнать, используется ли даже весь тип (класс / и т. Д.) Или нет.
В .NET / Java / JavaScript и других средах, управляемых во время выполнения, нет ничего, что могло бы остановить загрузку типов через отражение. Это популярно в структурах внедрения зависимостей, и еще труднее рассуждать перед десериализацией или динамической загрузкой модулей.
Компилятор не может знать, будут ли загружены такие типы. Их имена могут быть получены из внешних конфигурационных файлов во время выполнения.
Возможно, вы захотите поискать дрожание дерева, которое является общим термином для инструментов, которые пытаются безопасно удалить неиспользуемые подграфы кода.
источник
Взять на себя функцию
Можете ли вы доказать, что
actnumber
никогда не будет2
так, чтоAction2()
никогда не называется ...?источник
Action2()
никогда не будет вызвано», невозможно доказать это утверждение на практике - не может быть полностью решено компилятором . Разница в том, что «существует число X» против «мы можем записать число X в десятичном виде». Для некоторых X последнее никогда не произойдет, хотя первое верно.actnumber==2
. Этот ответ просто утверждает, что это трудно, даже не заявив о сложности.Я не согласен с проблемой остановки. Я бы не назвал такой код мертвым, хотя в действительности он никогда не будет достигнут.
Вместо этого давайте рассмотрим:
(Игнорировать ошибки типа и переполнения) Мертвый код?
источник
Посмотрите на этот пример:
Компилятор не может знать, что int может быть только четным или нечетным. Поэтому компилятор должен уметь понимать семантику вашего кода. Как это должно быть реализовано? Компилятор не может гарантировать, что самый низкий возврат никогда не будет выполнен. Поэтому компилятор не может обнаружить мертвый код.
источник
return i%2==0;
.i % 2 == 0
иi % 2 != 0
даже не требует рассуждения о значении целого по модулю константы (что все еще легко сделать), он требует только общего исключения подвыражения и общего принципа (даже канонизации), которыйif (cond) foo; if (!cond) bar;
можно упростить доif (cond) foo; else bar;
. Конечно, «понимание семантики» является очень сложной проблемой, но этот пост не показывает, что это так, и не показывает, что решение этой сложной проблемы необходимо для обнаружения мертвого кода.i % 2
и выведет его во временную переменную. Затем он распознает, что эти дваif
оператора являются взаимоисключающими и могут быть записаны какif(a==0)...else...
, а затем обнаружит, что все возможные пути выполнения проходят через первые дваreturn
оператора, и поэтому третийreturn
оператор является мертвым кодом. ( Хороший оптимизирующий компилятор еще более агрессивен: GCC превратил мой тестовый код в пару операций с битами).if (availableMemory()<0) then {dead code}
.{dead code}
часть. GCC обнаруживает это, доказывая неизбежное переполнение целых чисел со знаком. Поэтому весь код этой дуги в графе выполнения является мертвым кодом. GCC может даже удалить условную ветвь, которая ведет к этой дуге.