resueltos - ¿Son las funciones anidadas algo malo en gcc?
funcion si con dos condiciones (11)
Como la mayoría de las técnicas de programación, las funciones anidadas se deben usar cuando y solo cuando sean apropiadas.
No está obligado a usar este aspecto, pero si lo desea, las funciones anidadas reducen la necesidad de pasar parámetros al acceder directamente a las variables locales de la función que los contienen. Eso es conveniente. El uso cuidadoso de los parámetros "invisibles" puede mejorar la legibilidad. El uso descuidado puede hacer que el código sea mucho más opaco.
Evitar algunos o todos los parámetros hace que sea más difícil reutilizar una función anidada en otro lugar porque cualquier nueva función contenedora tendría que declarar esas mismas variables. La reutilización suele ser buena, pero muchas funciones nunca se reutilizarán, por lo que a menudo no importa.
Dado que el tipo de una variable se hereda junto con su nombre, la reutilización de funciones anidadas puede proporcionarle un polimorfismo económico, como una versión limitada y primitiva de plantillas.
El uso de funciones anidadas también presenta el peligro de errores si una función accede involuntariamente o cambia una de las variables de su contenedor. Imagine un bucle for que contenga una llamada a una función anidada que contenga un bucle for utilizando el mismo índice sin una declaración local. Si estuviera diseñando un lenguaje, incluiría funciones anidadas pero requeriría una declaración "heredar x" o "heredar const x" para que sea más obvio lo que está sucediendo y para evitar la herencia y modificación no intencionadas.
Hay varios otros usos, pero quizás lo más importante que tienen las funciones anidadas es permitir funciones auxiliares internas que no son visibles externamente, una extensión de las funciones estáticas no externas de C y C ++ o de las funciones privadas no públicas de C ++. Tener dos niveles de encapsulación es mejor que uno. También permite la sobrecarga local de nombres de funciones, por lo que no necesita nombres largos que describan en qué tipo funciona cada uno.
Hay complicaciones internas cuando una función que contiene almacena un puntero a una función contenida, y cuando se permiten múltiples niveles de anidación, pero los escritores de compiladores han estado lidiando con esos problemas durante más de medio siglo. No hay problemas técnicos que dificulten la adición a C ++ que a C, pero los beneficios son menores.
La portabilidad es importante, pero gcc está disponible en muchos entornos, y al menos otra familia de compiladores admite funciones anidadas: xlc de IBM disponible en AIX, Linux en PowerPC, Linux en BlueGene, Linux on Cell y z / OS. Consulte http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htm
Las funciones anidadas están disponibles en algunos lenguajes nuevos (p. Ej., Python) y muchos más tradicionales, como Ada, Pascal, Fortran, PL / I, PL / IX, Algol y COBOL. C ++ incluso tiene dos versiones restringidas: los métodos en una clase local pueden acceder a las variables estáticas (pero no automáticas) de su función contenedora, y los métodos en cualquier clase pueden acceder a los miembros y métodos de datos de clase estática. El próximo estándar de C ++ tiene funciones lamda, que en realidad son funciones anidadas anónimas. Así que el mundo de la programación tiene mucha experiencia a favor y en contra de ellos.
Las funciones anidadas son útiles pero cuídate. Siempre use cualquier característica y herramienta donde ayuden, no donde lastimen.
Sé que las funciones anidadas no son parte del estándar C, pero como están presentes en gcc (y el hecho de que gcc sea el único compilador que me importa), tiendo a usarlas con bastante frecuencia.
Esto es malo ? Si es así, ¿podría mostrarme algunos ejemplos desagradables? ¿Cuál es el estado de las funciones anidadas en gcc? ¿Van a ser eliminados?
Como usted dijo, son algo malo en el sentido de que no son parte del estándar C, y como tales no son implementados por muchos (¿algunos?) Otros compiladores C.
También tenga en cuenta que g ++ no implementa funciones anidadas, por lo que deberá eliminarlas si alguna vez necesita tomar parte de ese código y volcarlo en un programa de C ++.
Estoy de acuerdo con el ejemplo de Stefan, y la única vez que usé funciones anidadas (y luego las declaro en inline
) es en una ocasión similar.
También sugeriría que rara vez debería usar funciones en línea anidadas, y las pocas veces que las use debería tener (en su mente y en algún comentario) una estrategia para deshacerse de ellas (tal vez incluso implementarlas con el condicional #ifdef __GCC__
Compilacion).
Pero GCC es un compilador gratuito (como en el habla), hace alguna diferencia ... Y algunas extensiones GCC tienden a convertirse en estándares de facto y son implementadas por otros compiladores.
Otra extensión de GCC que creo que es muy útil es el goto computado, es decir, la etiqueta como valores . Al codificar autómatas o intérpretes de código de bytes es muy útil.
Hay veces en que las funciones anidadas pueden ser útiles, particularmente con algoritmos que barajan muchas variables. Algo así como una ordenación combinada de 4 vías escrita podría necesitar mantener muchas variables locales y tener una cantidad de piezas de código repetido que usen muchas de ellas. Llamar a esos bits de código repetido como una rutina de ayuda externa requeriría pasar una gran cantidad de parámetros y / o la rutina de ayuda acceder a ellos a través de otro nivel de direccionamiento indirecto de puntero.
Bajo tales circunstancias, podría imaginar que las rutinas anidadas podrían permitir una ejecución más eficiente del programa que otros medios para escribir el código, al menos si el compilador optimiza para la situación en la que existe alguna recursión que se realiza mediante una nueva llamada a la función más externa; Las funciones en línea, si el espacio lo permite, pueden ser mejores en las CPU que no están en caché, pero el código más compacto ofrecido por tener rutinas separadas puede ser útil. Si las funciones internas no pueden llamarse a sí mismas o recursivamente, pueden compartir un marco de pila con la función externa y, por lo tanto, podrían acceder a sus variables sin la penalización de tiempo de una desreferencia de puntero adicional.
Dicho todo esto, evitaría el uso de características específicas del compilador, excepto en circunstancias en las que el beneficio inmediato supere cualquier costo futuro que pueda resultar de tener que volver a escribir el código de otra manera.
Las funciones anidadas pueden ser malas, porque en condiciones específicas se desactivará el bit de seguridad NX (sin ejecución). Esas condiciones son:
Se utilizan GCC y funciones anidadas.
Se utiliza un puntero a la función anidada.
La función anidada accede a las variables desde la función principal.
La arquitectura ofrece protección de bits NX (sin ejecutar), por ejemplo, Linux de 64 bits.
Cuando se cumplan las condiciones anteriores, GCC creará un trampolín https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html . Para soportar trampolines, la pila se marcará ejecutable. consulte: https://www.win.tue.nl/~aeb/linux/hh/protection.html
Deshabilitar el bit de seguridad NX crea varios problemas de seguridad, y el más notable es que la protección de desbordamiento de búfer está deshabilitada. Específicamente, si un atacante colocó algún código en la pila (por ejemplo, como parte de una imagen, matriz o cadena configurable por el usuario), y se produjo una saturación del búfer, entonces se podría ejecutar el código de los atacantes.
Las funciones anidadas realmente no hacen nada que no se pueda hacer con las no anidadas (es por eso que ni C ni C ++ las proporcionan). Usted dice que no está interesado en otros compiladores; bueno, esto puede ser cierto en este momento, pero ¿quién sabe lo que traerá el futuro? Los evitaría, junto con todas las demás "mejoras" de GCC.
Una pequeña historia para ilustrar esto: solía trabajar para un Polytechinc del Reino Unido que usaba principalmente cajas DEC, específicamente un DEC-10 y algunos VAXen. Todos los profesores de ingeniería utilizaron las numerosas extensiones de DEC para FORTRAN en su código; estaban seguros de que seguiríamos siendo una tienda de DEC para siempre. Y luego reemplazamos el DEC-10 con un mainframe de IBM, cuyo compilador de FORTRAN no era compatible con ninguna de las extensiones. Hubo muchos lamentos y crujir de dientes ese día, te lo aseguro. Mi propio código FORTRAN (un simulador 8080) se transfirió a IBM en un par de horas (casi todo lo que aprendí a manejar el compilador de IBM), porque lo había escrito en el estándar FORTRAN-77.
Las funciones anidadas se pueden usar para hacer que un programa sea más fácil de leer y comprender, al reducir la cantidad de paso de parámetros explícitos sin introducir mucho estado global.
Por otro lado, no son portátiles a otros compiladores. (Tenga en cuenta los compiladores, no los dispositivos. No hay muchos lugares donde gcc no se ejecute).
Entonces, si ve un lugar donde puede hacer que su programa sea más claro al usar una función anidada, debe preguntarse a sí mismo "Estoy optimizando la portabilidad o la legibilidad".
Las funciones anidadas son obligatorias en cualquier lenguaje de programación serio.
Sin ellos, el sentido real de las funciones no es utilizable.
Se llama ámbito léxico.
Llegué tarde a la fiesta, pero no estoy de acuerdo con la afirmación de la respuesta aceptada de que
Las funciones anidadas realmente no hacen nada que no se pueda hacer con las no anidadas.
Específicamente:
TL; DR: Las funciones anidadas pueden reducir el uso de la pila en entornos integrados
Las funciones anidadas le dan acceso a variables de ámbito léxico como variables "locales" sin necesidad de empujarlas en la pila de llamadas. Esto puede ser realmente útil cuando se trabaja en un sistema con recursos limitados, por ejemplo, sistemas integrados. Considere este ejemplo artificial:
void do_something(my_obj *obj) {
double times2() {
return obj->value * 2.0;
}
double times4() {
return times2() * times2();
}
...
}
Tenga en cuenta que una vez que esté dentro de do_something (), debido a las funciones anidadas, las llamadas a times2 () y times4 () no necesitan insertar ningún parámetro en la pila, solo devuelven direcciones (y los compiladores inteligentes incluso los optimizan cuando posible).
Imagínese si hubiera muchos estados a los que debían acceder las funciones internas. Sin funciones anidadas, todo ese estado tendría que pasarse en la pila a cada una de las funciones. Las funciones anidadas le permiten acceder al estado como variables locales.
Necesito funciones anidadas que me permitan usar el código de utilidad fuera de un objeto.
Tengo objetos que cuidan varios dispositivos de hardware. Son estructuras que se pasan por puntero como parámetros a las funciones miembro, más bien como sucede automáticamente en c ++.
Asi podria tener
static int ThisDeviceTestBram( ThisDeviceType *pdev )
{
int read( int addr ) { return( ThisDevice->read( pdev, addr ); }
void write( int addr, int data ) ( ThisDevice->write( pdev, addr, data ); }
GenericTestBram( read, write, pdev->BramSize( pdev ) );
}
GenericTestBram no sabe ni puede saber acerca de ThisDevice, que tiene múltiples instancias. Pero todo lo que necesita es un medio de lectura y escritura, y un tamaño. ThisDevice-> read (...) y ThisDevice-> Write (...) necesitan el puntero a ThisDeviceType para obtener información sobre cómo leer y escribir la memoria de bloque (Bram) de esta instanciación particular. El puntero, pdev, no puede tener un scobe global, ya que existen múltiples instancias, y estas pueden ejecutarse simultáneamente. Dado que el acceso se produce a través de una interfaz FPGA, no es una cuestión simple de pasar una dirección y varía de un dispositivo a otro.
El código GenericTestBram es una función de utilidad:
int GenericTestBram( int ( * read )( int addr ), void ( * write )( int addr, int data ), int size )
{
// Do the test
}
Por lo tanto, el código de prueba debe escribirse solo una vez y no es necesario conocer los detalles de la estructura del dispositivo que llama.
Incluso con GCC, sin embargo, no puedes hacer esto. El problema es el puntero fuera de alcance, el mismo problema que se necesita resolver. La única forma que conozco para que f (x, ...) tenga conocimiento implícito de su padre es pasar un parámetro con un valor fuera de rango:
static int f( int x )
{
static ThisType *p = NULL;
if ( x < 0 ) {
p = ( ThisType* -x );
}
else
{
return( p->field );
}
}
return( whatever );
La función f puede ser inicializada por algo que tiene el puntero, luego puede ser llamada desde cualquier lugar. Aunque no es ideal.
Solo estoy explorando un tipo diferente de uso de funciones anidadas. Como un enfoque para la "evaluación perezosa" en C.
Imagina tal código:
void vars()
{
bool b0 = code0; // do something expensive or to ugly to put into if statement
bool b1 = code1;
if (b0) do_something0();
else if (b1) do_something1();
}
versus
void funcs()
{
bool b0() { return code0; }
bool b1() { return code1; }
if (b0()) do_something0();
else if (b1()) do_something1();
}
De esta manera, obtendrá claridad (bueno, podría ser un poco confuso cuando vea dicho código por primera vez), mientras que el código aún se ejecuta cuando y solo si es necesario. Al mismo tiempo, es bastante sencillo volver a convertirlo a la versión original.
Aquí surge un problema si se usa el mismo ''valor'' varias veces. GCC fue capaz de optimizar una ''llamada'' única cuando todos los valores se conocen en tiempo de compilación, pero supongo que eso no funcionaría para llamadas de función no triviales o algo así. En este caso, se podría utilizar el "almacenamiento en caché", pero esto se agrega a la no legibilidad.