Leí esta línea en un libro:
Probablemente es imposible construir un compilador que realmente pueda determinar si una función de C ++ cambiará o no el valor de una variable en particular.
El párrafo hablaba de por qué el compilador es conservador al verificar la consistencia.
¿Por qué es imposible construir tal compilador?
El compilador siempre puede comprobar si se reasigna una variable, si se invoca una función no constante en ella o si se pasa como un parámetro no constante ...
c++
compiler-construction
Jugador de críquet
fuente
fuente
Respuestas:
Por la misma razón que no puede escribir un programa que determine si un programa dado terminará. Esto se conoce como el problema de la detención y es una de esas cosas que no se pueden calcular.
Para ser claros, puede escribir un compilador que pueda determinar que una función cambia la variable en algunos casos , pero no puede escribir uno que le diga de manera confiable que la función cambiará o no la variable (o se detendrá) por todas las funciones posibles.
Aquí tienes un ejemplo sencillo:
¿Cómo puede un compilador determinar, con solo mirar ese código, si
foo
alguna vez cambiaráa
? Si lo hace o no depende de condiciones externas a la función, es decir, la implementación debar
. Hay más que eso en la prueba de que el problema de la detención no es computable, pero ya está bien explicado en el artículo de Wikipedia vinculado (y en todos los libros de texto de teoría de la computación), así que no intentaré explicarlo correctamente aquí.fuente
Imagínese que tal compilador existe. Supongamos también que, por conveniencia, proporciona una función de biblioteca que devuelve 1 si la función pasada modifica una variable dada y 0 cuando la función no lo hace. Entonces, ¿qué debería imprimir este programa?
fuente
f
modifica la variable, no si podría modificar la variable. Esta respuesta es correcta.modifies_variable
la fuente del compilador, anulando totalmente su argumento. (suponiendo que sea de código abierto, pero el punto debe ser claro)No confunda "modificará o no una variable dadas estas entradas" porque "tiene una ruta de ejecución que modifica una variable".
El primero se denomina determinación de predicado opaco y es trivialmente imposible de decidir; aparte de la reducción del problema de la detención, puede señalar que las entradas pueden provenir de una fuente desconocida (por ejemplo, el usuario). Esto es cierto para todos los lenguajes, no solo C ++.
Sin embargo, la última declaración se puede determinar mirando el árbol de análisis, que es algo que hacen todos los compiladores de optimización. La razón por la que lo hacen es que las funciones puras (y las funciones referencialmente transparentes , para alguna definición de referencialmente transparente ) tienen todo tipo de optimizaciones agradables que se pueden aplicar, como ser fácilmente insertables o tener sus valores determinados en tiempo de compilación; pero para saber si una función es pura, necesitamos saber si alguna vez podrá modificar una variable.
Entonces, lo que parece ser una declaración sorprendente sobre C ++ es en realidad una declaración trivial sobre todos los lenguajes.
fuente
Creo que la palabra clave en "si una función de C ++ cambiará o no el valor de una variable en particular" es "voluntad". Ciertamente es posible construir un compilador que verifique si una función de C ++ puede cambiar el valor de una variable en particular, no se puede decir con certeza que el cambio va a ocurrir:
fuente
const
controles de estado.No creo que sea necesario invocar el problema de la detención para explicar que no se puede saber algorítmicamente en el momento de la compilación si una función determinada modificará una determinada variable o no.
En cambio, es suficiente señalar que el comportamiento de una función a menudo depende de las condiciones de tiempo de ejecución, que el compilador no puede conocer de antemano. P.ej
¿Cómo podría el compilador predecir con certeza si
y
se modificará?fuente
Se puede hacer y los compiladores lo hacen todo el tiempo para algunas funciones , esto es, por ejemplo, una optimización trivial para accesos en línea simples o muchas funciones puras.
Lo que es imposible es conocerlo en el caso general.
Siempre que haya una llamada al sistema o una llamada a una función proveniente de otro módulo, o una llamada a un método potencialmente anulado, cualquier cosa podría suceder, incluida la toma de control hostil por el uso de un hacker de un desbordamiento de pila para cambiar una variable no relacionada.
Sin embargo, debe usar const, evitar globales, preferir referencias a punteros, evitar reutilizar variables para tareas no relacionadas, etc., lo que facilitará la vida del compilador al realizar optimizaciones agresivas.
fuente
Hay varias vías para explicar esto, una de las cuales es el problema de detención :
Si escribo un programa que se parece a esto:
¿El valor del
x
cambio? Para determinar esto, primero tendría que determinar si lado tons of complex stuff
pieza provoca que se active la condición o, lo que es más básico, si se detiene. Eso es algo que el compilador no puede hacer.fuente
¡Realmente sorprendido de que no haya una respuesta que usar el problema de la detención directamente! Hay una reducción muy sencilla de este problema al problema de la detención.
Imagine que el compilador pudiera decir si una función cambió o no el valor de una variable. Entonces ciertamente podría decir si la siguiente función cambia el valor de y o no, asumiendo que el valor de x se puede rastrear en todas las llamadas durante el resto del programa:
Ahora, para cualquier programa que nos guste, vamos a reescribirlo como:
Observe que, si, y solo si, nuestro programa cambia el valor de y, entonces termina - foo () es lo último que hace antes de salir. ¡Esto significa que hemos resuelto el problema de la detención!
Lo que nos muestra la reducción anterior es que el problema de determinar si el valor de una variable cambia es al menos tan difícil como el problema de la detención. Se sabe que el problema de la detención es incomputable, por lo que este también debe serlo.
fuente
y
. Me parece quefoo()
regresa rápidamente y luegomain()
sale. (Además, llamasfoo()
sin discutir ... eso es parte de mi confusión.)Tan pronto como una función llama a otra función de la que el compilador no "ve" la fuente, debe asumir que la variable ha cambiado o las cosas pueden ir mal más adelante. Por ejemplo, digamos que tenemos esto en "foo.cpp":
y tenemos esto en "bar.cpp":
¿Cómo puede el compilador "saber" que
x
no está cambiando (o ESTÁ cambiando, más apropiadamente)bar
?Estoy seguro de que podemos llegar a algo más complejo, si esto no es lo suficientemente complejo.
fuente
const_cast
in foo, aún haríax
cambios; estaría incumpliendo el contrato que dice que no debes cambiar lasconst
variables, pero como puedes convertir cualquier cosa a "más const", yconst_cast
existe, los diseñadores del lenguaje seguramente tenían en mente la idea de que a veces hay buenas razones para creer que losconst
valores pueden necesitar un cambio.Es imposible, en general, para el compilador para determinar si la variable se puede cambiar, ya que se han señalado.
Al verificar la constancia, la pregunta de interés parece ser si la variable puede ser cambiada por una función. Incluso esto es difícil en idiomas que admiten punteros. No puede controlar lo que hace otro código con un puntero, incluso podría leerse desde una fuente externa (aunque es poco probable). En lenguajes que restringen el acceso a la memoria, este tipo de garantías pueden ser posibles y permiten una optimización más agresiva que C ++.
fuente
Para hacer la pregunta más específica, sugiero que el siguiente conjunto de restricciones puede haber sido lo que el autor del libro pudo haber tenido en mente:
En el contexto del diseño del compilador, creo que las suposiciones 1, 3, 4 tienen perfecto sentido en la vista de un escritor de compiladores en el contexto de la corrección de generación de código y / o la optimización de código. El supuesto 2 tiene sentido en ausencia de la palabra clave volátil. Y estas suposiciones también enfocan la pregunta lo suficiente como para hacer que juzgar una respuesta propuesta sea mucho más definitiva :-)
Dadas esas suposiciones, una razón clave por la que no se puede suponer la const-ness se debe al aliasing variable. El compilador no puede saber si otra variable apunta a la variable const. La creación de alias podría deberse a otra función en la misma unidad de compilación, en cuyo caso el compilador podría buscar entre funciones y usar un árbol de llamadas para determinar estáticamente que podría producirse la creación de alias. Pero si el alias se debe a una biblioteca u otro código externo, entonces el compilador no tiene forma de saber al ingresar la función si las variables tienen alias.
Se podría argumentar que si una variable / argumento está marcado como constante, no debería estar sujeto a cambios mediante alias, pero para un escritor de compiladores eso es bastante arriesgado. Incluso puede ser arriesgado para un programador humano declarar una variable const como parte de, digamos, un gran proyecto en el que no conoce el comportamiento de todo el sistema, o el sistema operativo, o una biblioteca, para saber realmente que una variable ganó ' t cambiar.
fuente
Incluso si se declara una variable
const
, no significa que un código mal escrito pueda sobrescribirla.salida:
fuente
a
yb
son variables de pila, yb[1]
resulta que son la misma ubicación de memoria quea
.const
si todo está etiquetadoconst
. Es porque el comportamiento indefinido es parte de C / C ++. Estaba tratando de encontrar una forma diferente de responder a su pregunta en lugar de mencionar el problema de la detención o la intervención humana externa.Para ampliar mis comentarios, el texto de ese libro no está claro, lo que confunde el problema.
Como comenté, ese libro está tratando de decir: "Consigamos un número infinito de monos para escribir todas las funciones concebibles de C ++ que se puedan escribir. Habrá casos en los que si elegimos una variable que (alguna función particular que escribieron los monos) utiliza, no podemos determinar si la función cambiará esa variable ".
Por supuesto, para algunas (incluso muchas) funciones en cualquier aplicación, esto puede ser determinado por el compilador y muy fácilmente. Pero no para todos (o necesariamente para la mayoría).
Esta función se puede analizar fácilmente:
"foo" claramente no modifica "global". No modifica nada en absoluto, y un compilador puede resolver esto muy fácilmente.
Esta función no se puede analizar así:
Dado que las acciones de "foo" dependen de un valor que puede cambiar en tiempo de ejecución , es evidente que no se puede determinar en tiempo de compilación. si modificará "global".
Todo este concepto es mucho más sencillo de entender de lo que los científicos informáticos creen. Si la función puede hacer algo diferente en función de las cosas que pueden cambiar en tiempo de ejecución, entonces no puede averiguar qué hará hasta que se ejecute, y cada vez que se ejecute puede hacer algo diferente. Ya sea demostrablemente imposible o no, obviamente es imposible.
fuente