2012-01-06 13 views
8

Hace muchos meses, tuve que arreglar un código que causaba algunos problemas. El código se veía básicamente lo siguiente:¿Por qué esta recursión infinita obvia no da una advertencia del compilador?

int badFun() { return badFun(); }

Obviamente, esto causó un desbordamiento de pila, incluso en el lenguaje de alto nivel que estaba trabajando con (4Test en SilkTest). No hay forma de que este código pueda ser visto como beneficioso. La primera señal de problemas fueron las advertencias que se observaron después de que el script finalizó, pero no hubo errores de compilación o advertencias. Curiosamente, intenté escribir programas en C++, C# y Python con la misma estructura, y todos ellos compilados/interpretados sin errores de sintaxis o advertencias, incluso a pesar de que hubo errores de tiempo de ejecución en todos los casos. Ni siquiera vi ninguna advertencia en ninguno de estos casos. ¿Por qué no se ve esto como un posible problema por defecto?

EDIT: intenté escribir el equivalente de esa función en los tres idiomas, así que agregué esas etiquetas de función. Estoy más interesado en las razones generales por las que este código funciona sin advertencias. Por favor vuelva a etiquetar si es necesario.

+4

¿Su pregunta es sobre C#, C++ * o * Python? La muestra del código se parece mucho a C# para mí. – BoltClock

+10

No es tarea del compilador evitar que haga algo estúpido. En la mayoría de los idiomas, es posible que algún evento externo provoque que un constructo como este salga. –

+0

En MSVC (C++), le dará una advertencia si llama a la función: 'advertencia C4717: 'badFun': recursivo en todas las rutas de control, la función provocará el desbordamiento de la pila en tiempo de ejecución' – Mysticial

Respuesta

39

Este es el problema: las advertencias del compilador son características. Las características requieren esfuerzo, y el esfuerzo es una cantidad finita. (Puede medirse en dólares o puede medirse en el número de horas que alguien está dispuesto a dar a un proyecto de código abierto, pero le aseguro que es finito.)

Por lo tanto, tenemos que presupuestar ese esfuerzo. Cada hora que pasamos diseñando, implementando, probando y depurando una característica es una hora que podríamos haber pasado haciendo otra cosa. Por lo tanto, somos muy cuidadosos al decidir qué características agregar.

Eso es cierto para todas las funciones. Las advertencias tienen preocupaciones adicionales especiales. Debe haber una advertencia sobre el código que tiene las siguientes características:

  • Legal. Obviamente, el código tiene que ser legal; si no es legal, no es una advertencia en primer lugar, es un error.
  • Casi seguro que está mal. Una advertencia que advierte sobre el código correcto y deseable es una mala advertencia. (Además, si el código es correcto, debe haber una forma de escribir el código para que la advertencia desaparezca).
  • Inobvious. Las advertencias deben informarle acerca de los errores que son sutiles, en lugar de obvios.
  • Respetuoso para el análisis. Algunas advertencias son simplemente imposibles; una advertencia que requiere que el compilador resuelva El problema de la detención, por ejemplo, no va a suceder ya que eso es imposible.
  • Es poco probable que otras formas de prueba lo atrapen.

En su ejemplo específico, vemos que se cumplen algunas de estas condiciones. El código es legal, y casi seguro está equivocado. ¿Pero es inobvious? Alguien puede mirar el código fácilmente y ver que es una recursión infinita; la advertencia no ayuda mucho. ¿Es susceptible de análisis? El ejemplo trivial que das es, pero el problema general de encontrar recursiones ilimitadas es equivalente a resolver el problema de detención. ¿Es poco probable que te atrapen otras formas de prueba? No. En el momento en que ejecuta ese código en su caso de prueba, obtendrá una excepción que le dirá exactamente lo que está mal.

Por lo tanto, no vale la pena hacer esa advertencia. Hay mejores formas en que podríamos gastar ese presupuesto.

+2

+1 Excelente respuesta y realmente entiende lo que estaba preguntando. Sé que los compiladores no deberían impedir que escribas un código horrible, pero los buenos compiladores deberían arrojar al menos un "oye, ¿qué haces ahí?" a veces. – joshin4colours

+2

Existe el problema relacionado con 'int BadProperty {get {return BadProperty;}}' y su equivalente setter, donde el error es muy fácil de hacer, y no tan fácil de detectar. Pero este problema es claramente fácil de detectar cuando realmente se ejecuta el código, lo que reduce mucho el beneficio de esta característica. – CodesInChaos

+2

@CodeInChaos: De hecho, ese es un lugar donde la advertencia podría estar justificada. –

1

Porque el compilador no verificará este tipo de cosas.

Si instala un analizador de código como Resharper en Visual Studio, aparecerá una advertencia de llamada recursiva infinita o algo así en caso de que habilite la opción de análisis de código.

20

¿Por qué no se ve esto como un problema por defecto?

El error es un error de tiempo de ejecución, no un error de tiempo de compilación. El código es perfectamente válido, solo hace algo estúpido. El caso muy simple que le muestre sin duda podría ser detectado, pero muchos casos que podrían ser sólo un poco más complicada sería difícil de detectar: ​​

void evil() { 
    if (somethingThatTurnsOutToAlwaysBeTrue) 
     evil(); 
} 

Con el fin de determinar si eso es un problema, el compilador tiene que tratar de averiguar si la condición siempre será cierta o no. En el caso general, no creo que esto sea más computable que determinar si el programa finalmente se detendrá (es decir, es provably not computable).

+17

+1. Los compiladores no se molestan porque el problema es incomputable en el caso general. – Patrick87

+1

Por la misma lógica, un compilador no se molestaría en advertir sobre un código inalcanzable. Y sin embargo lo hace, en un subconjunto particular de casos. – harold

+0

@harold Como Eric Lippert explicó muy bien, se reduce a una decisión sobre el costo y el beneficio de producir la advertencia. Lo que traté de decir aquí es que la detección del caso trivial no parece muy útil, y los casos no triviales donde una advertencia sería útil probablemente sean difíciles o imposibles de detectar. – Caleb

1

Dudo que el compilador pueda detectar un fenómeno de tiempo de ejecución (desbordamiento de la pila) en tiempo de compilación. Hay muchos casos válidos para llamar a una función dentro de sí misma, recursividad. Pero, ¿cómo puede el compilador saber lo bueno de los malos casos de recursión?

A menos que tenga un poco de AI añadida, no creo que un compilador pueda detectar las diferencias entre la buena y la mala recursión, ese es el trabajo de un programador.

2

¿Cómo se supone que el compilador o intérprete debe saber qué hace la función? El alcance del compilador y el intérprete es compilar o interpretar el código sintáctico, no interpretar la semántica de su código.

Incluso si un compilador verificó esto, ¿dónde dibuja la línea? ¿Qué pasa si tienes una función recursiva que calcula factorial para siempre?

+1

El compilador sabe mucho sobre lo que está haciendo su función. Conoce todas las rutas de código en la función. Utiliza este conocimiento semántico para hacer análisis como detectar código inalcanzable, asegurando que una variable se inicialice antes de leer en todas las rutas de código, ... – CodesInChaos

3

Ningún compilador de ningún lenguaje de programación tiene algún tipo de idea acerca de la semántica del código que compila. Este es un código válido, aunque estúpido, por lo que se compilará.

+0

El compilador entiende mucho sobre la semántica del código. Sería bastante fácil implementar esta advertencia en un nivel razonable. Pero no sé si vale la pena el trabajo para especificar, implementar, documentar y mantener esa característica. – CodesInChaos

0

Como mencionaste El compilador simplemente verifica los errores sintácticos. la función recursiva es perfectamente válida sin ningún error.

en tiempo de ejecución,

cuando la pila está sobrevolado que arroja un error debido a desbordamiento de pila * no a causa de código *.

La función recursiva es perfetly válida, pero nuevamente en la implementación tenemos que poner la verificación de condición para devolver los valores antes de que se llene la pila.

Cuestiones relacionadas