2009-08-28 12 views
11

No parece que sería demasiado difícil de implementar en el montaje.¿Por qué las funciones anidadas no son compatibles con el estándar C?

gcc también tiene un indicador (-fnested-functions) para permitir su uso.

+2

Existe una diferencia entre lo que sería/no sería "demasiado difícil" de implementar y lo que el organismo de definición de estándares eligió para incluir u omitir del estándar. GCC proporciona soporte para esto, solo significa que es algo implementado fuera del alcance de la norma, es decir, es una característica no estándar. GCC es solo una implementación de un compilador que se adhiere al estándar C; no está limitado de ninguna manera para proporcionar solo lo que está en el estándar. –

+1

@Matt: suena como una respuesta en lugar de un comentario – AnthonyWJones

+0

Matt Ball: Mi pregunta es, de hecho, por qué el organismo que define los estándares optó por omitir esa característica. Mi referencia a gcc fue más un ejemplo de "se puede hacer así que ¿por qué no?". – des4maisons

Respuesta

11

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()".

Si desea acceder a las variables del ámbito que lo contiene, básicamente lo está forzando 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 al generar (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.

+0

lol @ tus primeros 2 párrafos - son los mismos que los míos – Claudiu

+0

Podrías prohibir tomar la dirección de una función anidada, ¿o no? – caf

+6

Las dos formas más comunes de implementar acceso variable de nivel superior en funciones anidadas son "enlaces estáticos", donde cada función tiene un puntero al marco de pila de su elemento primario léxico y "pantallas", que consisten en una matriz de punteros . Básicamente es una lista vinculada vs. una matriz fija. No necesita cierres completos a menos que desee permitir que se llame a una función interna después de que su padre haya salido (Pascal, por ejemplo, no lo permite). http://en.wikipedia.org/wiki/Call_stack#Lexically_nested_routines –

2

ANSI C se ha establecido por 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.

+1

Bastante seguro que quisiste decir años allí ... heh. –

+1

... o agregó 0. –

+0

Solucionado. . –

9

me gustaría quote something del BDFL (Guido van Rossum):

Esto se debe a que las definiciones de función anidada no tienen acceso a los variables locales del bloque que rodea - sólo a la globales del módulo que contiene . Esto se hace para que la búsqueda de globales no haga que tenga que recorrer una cadena de diccionarios, como en C, solo hay dos ámbitos anidados : locales y globales (y más allá de esto, incorporados). Por lo tanto, las funciones anidadas tienen un uso limitado. Esta fue una decisión deliberada de , basada en la experiencia con idiomas que permitían anidar arbitrarios como Pascal y ambos Algols - código con demasiado muchos ámbitos anidados es tan legible como código con demasiados GOTOs.

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 completamente anidadas), pero creo que la declaración aún se aplica.

La otra parte podría haber sido closures.

Si usted tiene una función como el código C-como:

(*int()) foo() { 
    int x = 5; 
    int bar() { 
     x = x + 1; 
     return x; 
    } 
    return &bar; 
} 

Si utiliza bar en una devolución de llamada de algún tipo, lo que sucede con x? Esto está bien definido en muchos lenguajes más nuevos y de más alto nivel, pero AFAIK no existe una forma bien definida de rastrear ese x en C - ¿bar devuelve 6 cada vez, o realiza llamadas sucesivas a bar valores de incremento de retorno? Eso podría haber agregado una nueva capa de complicación a la definición relativamente simple de C.

+3

Presumiblemente, si usaba la barra como devolución de llamada, estaría utilizando la dirección de una variable local (barra) después de que la función que la contiene haya retornado. Esto, como sería cierto si la barra fuera un int (por ejemplo), debería undefined. – des4maisons

+0

Pero la mayoría de los lenguajes con funciones de primer orden (Python y Lua por ejemplo) definen este comportamiento en el estilo de cierre clásico - 'bar' devolvería 5,6,7, ... Las funciones anidadas casi * requieren * funciones ser de primer orden v alues. –

+2

Eso es verdad ... hmm, no había considerado eso. Pero C y Python hacen cosas diferentes cuando devuelven también matrices "locales" (o Listas, en python). C le devolverá un puntero para apilar la memoria que ya no está bien definida; Python te devolverá una lista completamente nueva. Entonces son paradigmas diferentes, y no estoy seguro de que sean comparables. – des4maisons

3

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 o no fáciles de implementar. 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.

+0

Hmm ... al no haber recibido una respuesta definida de "esto es por qué", quizás tengas razón. Aun así, me gustaría que hubiera una buena razón. – des4maisons

+2

Desafortunadamente, la razón probablemente sea tan simple como "no hay suficientes personas atendidas por la función". El proceso de estándares C es generalmente uno de codificar la práctica existente. Como pocos compiladores lo han hecho alguna vez, nunca se ha insistido en que esté en el estándar. Dado que no ayuda mucho en C (ya que no tiene cosas como cierres o un alcance global anidado que lo haga realmente interesante tener funciones anidadas), nunca acaba de llegar allí. –

+0

La estandarización tenía para chárter para estandarizar la práctica existente. Hicieron más que eso, pero agregar funciones anidadas se habría percibido como ir muy lejos. Debe volver a los años 70 y la definición de C como un lenguaje de sistema, a la par con BCPL y BLISS y ensambladores. – AProgrammer

5

Ver C FAQ 20.24 y the GCC manual problemas potenciales:

Si intenta llamar a la función anidada a través de su dirección después de la función que contiene ha salido, todo se desatará el infierno.Si intenta llamar al , llámelo después de que haya salido un nivel de alcance , y si se refiere a algunas de las variables que ya no están en el alcance , puede tener suerte, pero no es el riesgo. Sin embargo, si la función anidada no se refiere a , cualquier cosa que haya salido del alcance, , debe estar seguro.

Esto no es realmente más grave que algunas otras partes problemáticas de la norma C, así que diría que las razones son en su mayoría histórica (C99 no es realmente que diferente de K & RC En cuanto a prestaciones) .

Existen algunos casos en los 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 que el compilador alinee correctamente tales funciones, es decir, una solución con una función separada será más prolija.

0

O bien no permite referencias a las variables locales de la función que contiene 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.

5

Las funciones anidadas son muy delicadas. ¿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.

+0

¿Cuál es el problema de tener variables asignadas por pila si las hacemos cierres? – Lazer

+0

Además, esto puede sonar estúpido, pero ¿por qué necesitas cierres con funciones anidadas? ¿Qué tiene de especial las funciones anidadas? ¡Gracias! – Lazer

+1

@Lazer: las variables asignadas a la pila desaparecen cuando la función retorna. Si tiene un cierre, la función debe tener acceso a esas variables incluso después de que vuelva, en caso de que se vuelva a llamar. Entonces tienes que ponerlos en algún lado además de la pila. ¿Por qué los necesitas en absoluto? Pregúntele a las personas que usan JavaScript, Python, Ruby, Scheme, LISP, C#, etc. Son útiles para ciertas cosas como la creación de controladores de eventos. También haga que su código sea más elegante en algunos casos. – Claudiu

-3

Su pregunta y su respuesta forman una tautología ... ¿Por qué? ¡Porque!

Me podría preguntar por qué C no tiene, oh no sé, pasar por referencia en lugar de pasar por valor de manera predeterminada. O cualquier otra cosa que no tenga.

Si desea funciones anidadas, ¡ya está bien! Encuentra y usa un lenguaje que los respalde.

1

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é sucede si necesita 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 de la que se devuelve.

1

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 es que una función puede tener gran muchas variables en el alcance, contando las variables de todas las funciones en las que se ha anidado. (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 trabajaban en el código que lo único peor que una variable global era una variable semi-global, y por favor no creas más. Lo hubiera excluido del idioma, si pudiera. Lamentablemente, no existía tal opción para el compilador ...

Cuestiones relacionadas