Я прочитал эту строчку в книге:
Доказано, что невозможно создать компилятор, который действительно может определить, изменит ли функция C ++ значение определенной переменной.
В абзаце говорилось о том, почему компилятор консервативен при проверке константности.
Почему невозможно построить такой компилятор?
Компилятор всегда может проверить, переназначена ли переменная, для нее вызывается неконстантная функция или передается ли она как неконстантный параметр ...
c++
compiler-construction
Игрок в крикет
источник
источник
Ответы:
По той же причине, что вы не можете написать программу, которая будет определять, завершится ли какая-либо данная программа. Это известно как проблема остановки , и это одна из тех вещей, которые невозможно вычислить.
Чтобы было ясно, вы можете написать компилятор, который может определять, что функция действительно изменяет переменную в некоторых случаях , но вы не можете написать такой, который надежно сообщает вам, что функция изменит или не изменит переменную (или остановится) для всевозможные функции.
Вот простой пример:
Как компилятор может определить, просто взглянув на этот код,
foo
изменится ли когда-нибудьa
? Будет ли это или нет, зависит от внешних по отношению к функции условий, а именно от реализацииbar
. Есть нечто большее, чем доказательство того, что проблема остановки не вычислима, но она уже хорошо объяснена в связанной статье Википедии (и в каждом учебнике теории вычислений), поэтому я не буду пытаться объяснять ее здесь правильно.источник
Представьте, что такой компилятор существует. Также предположим, что для удобства он предоставляет библиотечную функцию, которая возвращает 1, если переданная функция изменяет заданную переменную, и 0, если функция не изменяет. Тогда что должна печатать эта программа?
источник
f
изменяет ли переменную, а не может ли она изменить переменную. Это правильный ответ.modifies_variable
из источника компилятора, полностью аннулируя ваш аргумент. (предполагая открытый исходный код, но суть должна быть ясна)Не путайте «будет или не будет изменять переменную с учетом этих входных данных», поскольку «имеет путь выполнения, который изменяет переменную».
Первый из них называется непрозрачным определением предиката , и его тривиально невозможно решить - помимо сокращения проблемы остановки, вы можете просто указать, что входные данные могут поступать из неизвестного источника (например, пользователя). Это верно для всех языков, а не только для C ++.
Однако последнее утверждение можно определить, посмотрев на дерево синтаксического анализа, что и делают все оптимизирующие компиляторы. Причина, по которой они это делают, заключается в том, что чистые функции (и ссылочно прозрачные функции для некоторого определения ссылочно прозрачных ) имеют всевозможные хорошие оптимизации, которые можно применять, например, их легко встраивать или определять их значения во время компиляции; но чтобы знать, является ли функция чистой, нам нужно знать, может ли она когда-либо изменять переменную.
Итак, то, что кажется неожиданным заявлением о C ++, на самом деле является тривиальным заявлением обо всех языках.
источник
Я думаю, что ключевым словом в выражении «будет ли функция C ++ изменять значение конкретной переменной» является «будет». Конечно, можно создать компилятор, который проверяет, разрешено ли функции C ++ изменять значение конкретной переменной, вы не можете с уверенностью сказать, что это изменение произойдет:
источник
const
проверках -ness.Я не думаю, что необходимо вызывать проблему остановки, чтобы объяснить, что вы не можете алгоритмически узнать во время компиляции, будет ли данная функция изменять определенную переменную или нет.
Вместо этого достаточно указать, что поведение функции часто зависит от условий выполнения, о которых компилятор не может знать заранее. Например
Как компилятор мог с уверенностью предсказать,
y
будут ли изменены?источник
Это можно сделать, и компиляторы делают это все время для некоторых функций , например, это тривиальная оптимизация для простых встроенных средств доступа или многих чистых функций.
Что невозможно знать в общем случае.
Всякий раз, когда есть системный вызов или вызов функции, исходящий из другого модуля, или вызов потенциально замещаемого метода, может произойти все, что угодно, включая враждебный захват из-за использования хакером переполнения стека для изменения несвязанной переменной.
Однако вы должны использовать const, избегать глобальных переменных, предпочитать ссылки на указатели, избегать повторного использования переменных для несвязанных задач и т. Д., Что облегчит жизнь компилятору при выполнении агрессивных оптимизаций.
источник
Есть несколько способов объяснить это, одна из которых - проблема остановки :
Если я напишу программу, которая выглядит так:
Изменится ли стоимость
x
? Чтобы определить это, вам сначала нужно определить,do tons of complex stuff
вызывает ли деталь условие срабатывания или, что более важно, останавливается ли оно. Этого компилятор сделать не может.источник
Действительно удивлен, что нет ответа, который бы использовал проблему остановки напрямую! Существует очень прямое сокращение от этой проблемы до проблемы остановки.
Представьте, что компилятор может определить, изменила ли функция значение переменной. Тогда он определенно сможет определить, изменяет ли следующая функция значение y или нет, предполагая, что значение x можно отслеживать во всех вызовах остальной части программы:
Теперь для любой понравившейся программы давайте перепишем ее так:
Обратите внимание, что если и только если наша программа изменяет значение y, она затем завершается - foo () - это последнее, что она делает перед завершением. Это означает, что мы решили проблему остановки!
Приведенное выше сокращение показывает нам, что проблема определения того, изменяется ли значение переменной: не менее сложна, чем проблема остановки. Известно, что проблема остановки не поддается расчету, поэтому и эта проблема тоже должна быть.
источник
y
. Мне кажется, онfoo()
быстро возвращается, а затемmain()
уходит. (Кроме того, вы звонитеfoo()
без аргументов ... это часть моего замешательства.)Как только функция вызывает другую функцию, источник которой компилятор не «видит», она либо должна предположить, что переменная изменена, либо что-то может пойти не так, как описано ниже. Например, скажем, у нас есть это в "foo.cpp":
и у нас есть это в bar.cpp:
Как компилятор может «знать», что
x
не меняется (или, точнее, изменяется)bar
?Я уверен, что мы можем придумать что-то более сложное, если это будет недостаточно сложно.
источник
const_cast
в foo, он все равно внесетx
изменения - я нарушил бы контракт, в котором говорится, что вы не должны изменятьconst
переменные, но поскольку вы можете преобразовать что угодно в "more const" иconst_cast
существует, Разработчики языка наверняка имели в виду, что иногда есть веские причины полагать, чтоconst
ценности могут нуждаться в изменении.Как уже указывалось, компилятор не может определить, будет ли изменена переменная .
При проверке константный-ности, вопрос о том, интерес , как представляется, если переменная может быть изменена с помощью функции. Даже это сложно для языков, поддерживающих указатели. Вы не можете контролировать, что делает другой код с помощью указателя, его можно даже прочитать из внешнего источника (хотя маловероятно). В языках, ограничивающих доступ к памяти, эти типы гарантий возможны и допускают более агрессивную оптимизацию, чем это делает C ++.
источник
Чтобы сделать вопрос более конкретным, я предполагаю, что автор книги мог иметь в виду следующий набор ограничений:
В контексте разработки компилятора, я думаю, что предположения 1,3,4 имеют смысл с точки зрения автора компилятора в контексте правильности генерации кода и / или оптимизации кода. Предположение 2 имеет смысл при отсутствии ключевого слова volatile. И эти предположения также фокусируют вопрос достаточно, чтобы сделать оценку предлагаемого ответа гораздо более окончательной :-)
Учитывая эти предположения, основная причина, по которой нельзя предполагать постоянство, связана с сглаживанием переменных. Компилятор не может знать, указывает ли другая переменная на переменную const. Псевдонимы могут быть связаны с другой функцией в том же модуле компиляции, и в этом случае компилятор может просматривать функции и использовать дерево вызовов, чтобы статически определить, что наложение может возникнуть. Но если псевдоним связан с библиотекой или другим сторонним кодом, то компилятор не имеет возможности узнать при входе в функцию, имеют ли переменные псевдонимы.
Вы можете возразить, что если переменная / аргумент помечена как const, она не должна изменяться с помощью псевдонима, но для автора компилятора это довольно рискованно. Для программиста-человека может быть даже рискованно объявлять переменную const как часть, скажем, большого проекта, где он не знает поведения всей системы, ОС или библиотеки, чтобы действительно знать, что переменная выиграла. т изменить.
источник
Даже если переменная объявлена
const
, это не значит, что какой-то плохо написанный код может ее перезаписать.вывод:
источник
a
иb
являются переменными стека иb[1]
просто находятся в том же месте памяти, что иa
.const
ли что- то действительно, если все помеченоconst
. Это потому, что неопределенное поведение является частью C / C ++. Я пытался найти другой способ ответить на его вопрос, вместо того, чтобы упоминать проблему остановки или вмешательство человека.Чтобы расширить мои комментарии, текст этой книги неясен, что запутывает проблему.
Как я уже сказал, эта книга пытается сказать: «Давайте заставим бесконечное количество обезьян написать все мыслимые функции C ++, которые когда-либо могли быть написаны. Будут случаи, когда мы выберем переменную, которая (какую-то конкретную функцию, которую написали обезьяны) использует, мы не можем понять, изменит ли функция эту переменную ".
Конечно, для некоторых (даже многих) функций в любом приложении это может быть определено компилятором, и это очень легко. Но не для всех (или обязательно для большинства).
Эту функцию легко проанализировать так:
"foo" явно не изменяет "global". Он вообще ничего не меняет, и компилятор может с этим легко справиться.
Эту функцию нельзя так анализировать:
Поскольку действия "foo" зависят от значения, которое может изменяться во время выполнения , во время компиляции явно невозможно определить , изменит ли он "global".
Всю эту концепцию понять намного проще, чем предполагают компьютерные ученые. Если функция может делать что-то другое в зависимости от того, что может измениться во время выполнения, то вы не можете понять, что она будет делать, пока не запустится, и каждый раз, когда она запускается, она может делать что-то другое. Доказуемо это невозможно или нет, но очевидно невозможно.
источник