Puede hacer estas cosas, en gran parte porque en realidad no son tan difíciles de hacer.
Desde el punto de vista del compilador, tener una declaración de función dentro de otra función es bastante trivial de implementar. El compilador necesita un mecanismo que permita declaraciones dentro de funciones para manejar otras declaraciones (por ejemplo, int x;
) dentro de una función de todos modos.
Por lo general, tendrá un mecanismo general para analizar una declaración. Para el tipo que escribe el compilador, realmente no importa en absoluto si ese mecanismo se invoca al analizar el código dentro o fuera de otra función; es solo una declaración, por lo que cuando ve lo suficiente para saber que hay una declaración, invoca la parte del compilador que se ocupa de las declaraciones.
De hecho, prohibir estas declaraciones en particular dentro de una función probablemente agregaría complejidad adicional, porque el compilador necesitaría una verificación completamente gratuita para ver si ya está mirando el código dentro de una definición de función y, en base a eso, decidir si permitir o prohibir esta en particular. declaración.
Eso deja la cuestión de en qué se diferencia una función anidada. Una función anidada es diferente debido a cómo afecta la generación de código. En lenguajes que permiten funciones anidadas (por ejemplo, Pascal) normalmente se espera que el código de la función anidada tenga acceso directo a las variables de la función en la que está anidada. Por ejemplo:
int foo() {
int x;
int bar() {
x = 1;
}
}
Sin funciones locales, el código para acceder a las variables locales es bastante simple. En una implementación típica, cuando la ejecución ingresa a la función, se asigna en la pila algún bloque de espacio para las variables locales. Todas las variables locales se asignan en ese único bloque y cada variable se trata simplemente como un desplazamiento desde el principio (o el final) del bloque. Por ejemplo, consideremos una función como esta:
int f() {
int x;
int y;
x = 1;
y = x;
return y;
}
Un compilador (asumiendo que no optimizó el código adicional) podría generar código para esto aproximadamente equivalente a esto:
stack_pointer -= 2 * sizeof(int);
x_offset = 0;
y_offset = sizeof(int);
stack_pointer[x_offset] = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];
return_location = stack_pointer[y_offset];
stack_pointer += 2 * sizeof(int);
En particular, tiene una ubicación que apunta al comienzo del bloque de variables locales, y todo acceso a las variables locales es como compensaciones desde esa ubicación.
Con las funciones anidadas, ese ya no es el caso; en cambio, una función tiene acceso no solo a sus propias variables locales, sino a las variables locales de todas las funciones en las que está anidada. En lugar de solo tener un "stack_pointer" desde el cual calcula un desplazamiento, necesita caminar hacia arriba en la pila para encontrar los stack_pointers locales a las funciones en las que está anidado.
Ahora, en un caso trivial, eso tampoco es tan terrible: si bar
está anidado dentro de foo
, entonces bar
puede buscar la pila en el puntero de la pila anterior para acceder a foo
las variables. ¿Correcto?
¡Incorrecto! Bueno, hay casos en los que esto puede ser cierto, pero no es necesariamente el caso. En particular,bar
podría ser recursivo, en cuyo caso una determinada invocación debar
podría tener que buscar un número casi arbitrario de niveles en la pila para encontrar las variables de la función circundante. En términos generales, debe hacer una de dos cosas: o coloca algunos datos adicionales en la pila, para que pueda buscar una copia de seguridad en la pila en tiempo de ejecución para encontrar el marco de pila de la función que lo rodea, o bien pasa un puntero a el marco de pila de la función circundante como un parámetro oculto para la función anidada. Ah, pero tampoco hay necesariamente una sola función circundante: si puede anidar funciones, probablemente pueda anidarlas (más o menos) arbitrariamente en profundidad, por lo que debe estar listo para pasar una cantidad arbitraria de parámetros ocultos. Eso significa que normalmente termina con algo así como una lista vinculada de marcos de pila a funciones circundantes,
Eso, sin embargo, significa que el acceso a una variable "local" puede no ser un asunto trivial. Encontrar el marco de pila correcto para acceder a la variable puede no ser trivial, por lo que el acceso a las variables de las funciones circundantes también es (al menos normalmente) más lento que el acceso a las variables verdaderamente locales. Y, por supuesto, el compilador tiene que generar código para encontrar los marcos de pila correctos, acceder a las variables a través de cualquiera de un número arbitrario de marcos de pila, etc.
Esta es la complejidad que C evitaba al prohibir las funciones anidadas. Ahora bien, es cierto que un compilador de C ++ actual es un tipo de bestia bastante diferente de un compilador de C antiguo de los años 70. Con cosas como herencia virtual múltiple, un compilador de C ++ tiene que lidiar con cosas en esta misma naturaleza general en cualquier caso (es decir, encontrar la ubicación de una variable de clase base en tales casos también puede ser no trivial). Sobre una base porcentual, admitir funciones anidadas no agregaría mucha complejidad a un compilador de C ++ actual (y algunas, como gcc, ya las admiten).
Al mismo tiempo, tampoco suele aportar mucha utilidad. En particular, si desea definir algo que actúe como una función dentro de una función, puede usar una expresión lambda. Lo que esto crea en realidad es un objeto (es decir, una instancia de alguna clase) que sobrecarga la llamada a la función operator ( operator()
) pero aún brinda capacidades similares a funciones. Sin embargo, hace que capturar (o no) datos del contexto circundante sea más explícito, lo que le permite usar los mecanismos existentes en lugar de inventar un mecanismo completamente nuevo y un conjunto de reglas para su uso.
En pocas palabras: aunque inicialmente pueda parecer que las declaraciones anidadas son difíciles y las funciones anidadas son triviales, más o menos lo contrario es cierto: las funciones anidadas son en realidad mucho más complejas de soportar que las declaraciones anidadas.
one
es una definición de función , las otras dos son declaraciones .