valores - ¿Por qué las funciones anidadas no son compatibles con el estándar C?
funcion si con dos condiciones (10)
No parece que sería demasiado difícil de implementar en el montaje.
gcc también tiene un indicador (-fnested-functions) para habilitar su uso.
Consulte C FAQ 20.24 y el manual de GCC para problemas potenciales:
Si intenta llamar a la función anidada a través de su dirección después de que la función contenedora haya salido, todo el infierno se desatará. Si intentas llamar después de que ha salido un nivel de ámbito que lo contiene, y si se refiere a algunas de las variables que ya no están dentro del alcance, puede que tengas suerte, pero no es prudente correr el riesgo. Sin embargo, si la función anidada no se refiere a nada que haya salido del alcance, debe estar seguro.
Esto no es realmente más severo que algunas otras partes problemáticas del estándar C, así que diría que las razones son en su mayoría históricas (C99 no es tan diferente de K & R C).
Hay algunos casos en que las funciones anidadas con alcance léxico pueden ser útiles (considere una función interna recursiva que no necesita espacio de pila adicional para las variables en el ámbito externo sin la necesidad de una variable estática), pero con suerte puede confiar en el compilador para alinear correctamente tales funciones, es decir, una solución con una función separada será más prolija.
Las funciones anidadas son algo muy delicado. ¿Harás que cierren? De lo contrario, no tienen ninguna ventaja para las funciones normales, ya que no pueden acceder a ninguna variable local. Si lo hacen, ¿qué haces para apilar las variables asignadas? Tienes que ponerlos en otro lugar para que, si llamas a la función anidada más tarde, la variable aún esté allí. Esto significa que tomarán memoria, por lo que debe asignar espacio para ellos en el montón. Sin GC, esto significa que el programador ahora se encarga de limpiar las funciones. Etc ... C # hace esto, pero tienen un GC, y es un lenguaje considerablemente más nuevo que C.
Me gustaría citar algo de BDFL (Guido van Rossum):
Esto se debe a que las definiciones de funciones anidadas no tienen acceso a las variables locales del bloque circundante, solo a las globales del módulo contenedor. Esto se hace para que la búsqueda de globales no tenga que recorrer una cadena de diccionarios, como en C, solo hay dos ámbitos anidados: locales y globales (y más allá de esto, integrados). Por lo tanto, las funciones anidadas tienen un uso limitado. Esta fue una decisión deliberada, basada en la experiencia con idiomas que permiten la anidación de arbitrarios, como Pascal y los dos algoritmos: el código con demasiados ámbitos anidados es tan legible como el código con demasiados GOTO.
El énfasis es mío
Creo que se estaba refiriendo al alcance anidado en Python (y como David señala en los comentarios, esto fue desde 1993, y Python sí admite ahora funciones totalmente anidadas), pero creo que la declaración aún se aplica.
La otra parte podría haber sido cierres .
Si tiene una función como este código tipo C:
(*int()) foo() {
int x = 5;
int bar() {
x = x + 1;
return x;
}
return &bar;
}
Si usa la bar
en una devolución de llamada de algún tipo, ¿qué ocurre con x? Esto está bien definido en muchos idiomas nuevos y de más alto nivel, pero AFAIK no hay una forma bien definida de rastrear esa x
en C: ¿la bar
devuelve 6 cada vez o realiza llamadas sucesivas a los valores de incremento de la bar
? Eso podría haber agregado una nueva capa de complicación a la definición relativamente simple de C.
O bien, no permite referencias a variables locales de la función contenedora en el contenido, y la anidación es solo una característica de alcance sin mucho uso, o lo hace. Si lo hace, no es una característica tan simple: debe poder invocar una función anidada desde otra mientras accede a los datos correctos, y también debe tener en cuenta las llamadas recursivas. Eso no es imposible, las técnicas son bien conocidas y dominadas cuando se diseñó C (Algol 60 ya tenía la característica). Pero complica la organización en tiempo de ejecución y el compilador y evita una asignación simple al lenguaje ensamblador (un puntero de función debe llevar información sobre eso; bueno, hay alternativas como el uso de un gcc). Estaba fuera del alcance del lenguaje de implementación del sistema que C fue diseñado para ser.
Resulta que en realidad no son tan fáciles de implementar correctamente.
¿Debería una función interna tener acceso a las variables del ámbito contenedor? Si no, no tiene sentido anidarlo; simplemente hágalo estático (para limitar la visibilidad de la unidad de traducción en la que se encuentra) y agregue un comentario que diga "Esta es una función auxiliar utilizada solo por myfunc ()".
Sin embargo, si desea acceder a las variables del ámbito que lo contiene, básicamente lo está obligando a generar cierres (la alternativa es restringir lo que puede hacer con las funciones anidadas lo suficiente como para que no sirvan). Creo que GCC realmente maneja esto generando (en tiempo de ejecución) un procesador único para cada invocación de la función contenedora, que configura un puntero de contexto y luego llama a la función anidada. Esto termina siendo un truco bastante raro, y algo que algunas implementaciones perfectamente razonables no pueden hacer (por ejemplo, en un sistema que prohíbe la ejecución de la memoria grabable, que muchos sistemas operativos modernos hacen por razones de seguridad). La única forma razonable de hacerlo funcionar en general es forzar a todos los punteros de función a llevar un argumento de contexto oculto, y todas las funciones para aceptarlo (porque en el caso general no sabe cuándo lo llama si es un cierre o una función no cerrada). Esto es inapropiado para requerir en C por razones técnicas y culturales, por lo que estamos atascados con la opción de utilizar punteros de contexto explícitos para falsificar un cierre en lugar de funciones de anidamiento, o usar un lenguaje de nivel superior que tenga la infraestructura necesaria para hazlo apropiadamente.
Su pregunta y su respuesta forman una tautología ... ¿Por qué? ¡Porque!
También podría preguntar por qué C no tiene, oh no sé, pasar por referencia en lugar de pasar por valor por defecto. O cualquier otra cosa que no tenga.
Si quieres funciones anidadas, ¡bien en ti! Encuentra y usa un lenguaje que los respalde.
Tampoco sería demasiado difícil agregar funciones de miembros a las estructuras, pero tampoco están en el estándar.
Las características no se agregan al estándar C en función de si son fáciles de implementar o no. Es una combinación de muchos otros factores, incluido el momento en que se escribió el estándar y lo que era común / práctico en ese momento.
ANSI C se estableció hace 20 años. Quizás entre 1983 y 1989 el comité pudo haberlo discutido a la luz del estado de la tecnología del compilador en ese momento, pero si lo hicieron, su razonamiento se pierde en un pasado oscuro y distante.
No estoy de acuerdo con Dave Vandervies.
Definir una función anidada es un estilo de codificación mucho mejor que definirlo en el alcance global, por lo que es estático y agrega un comentario que dice "Esta es una función auxiliar utilizada solo por myfunc ()".
¿Qué pasa si necesitas una función auxiliar para esta función de ayuda? ¿Agregarías un comentario "Esta es una función auxiliar para la primera función auxiliar utilizada solo por myfunc"? ¿Dónde tomas los nombres necesarios para todas esas funciones sin contaminar el espacio de nombre por completo?
¿Qué tan confuso se puede escribir el código?
Pero, por supuesto, existe el problema de cómo lidiar con el cierre, es decir, devolver un puntero a una función que tiene acceso a las variables definidas en la función desde la que se devuelve.
Una razón más: no está del todo claro que las funciones anidadas sean valiosas. Hace veintitantos años solía hacer programación y mantenimiento a gran escala en (VAX) Pascal. Teníamos un montón de código antiguo que hacía un uso intensivo de funciones anidadas. Al principio, pensé que esto era genial (en comparación con K & R C, en el que había estado trabajando antes) y comencé a hacerlo yo mismo. Después de un rato, decidí que era un desastre y me detuve.
El problema era que una función podía tener muchas variables en su alcance, contando las variables de todas las funciones en las que estaba anidada. (Algunos códigos antiguos tenían diez niveles de anidación, cinco eran bastante comunes, y hasta que cambié de opinión, codifiqué algunos de estos últimos). Las variables en la pila de anidación podrían tener los mismos nombres, de modo que las variables locales de la función "interna" podría enmascarar variables del mismo nombre en más funciones "externas". Una variable local de una función, que en los lenguajes tipo C es totalmente privada, podría ser modificada por una llamada a una función anidada. El conjunto de posibles combinaciones de este jazz era casi infinito, y una pesadilla para comprender al leer el código.
Entonces, comencé a llamar a esta construcción de programación "variables semi-globales" en lugar de "funciones anidadas", y diciendo a otras personas que trabajan en el código que lo único peor que una variable global era una variable semi-global, y por favor no crean nunca más. Lo hubiera excluido del idioma, si pudiera. Lamentablemente, no hubo tal opción para el compilador ...