2012-05-01 9 views
40

Esta pregunta no es terriblemente específica; es realmente para mi propio enriquecimiento en C y espero que otros también lo puedan encontrar útil.Funciones anónimas utilizando las expresiones de declaración GCC

Descargo de responsabilidad: Sé que muchos tendrán el impulso de responder con "si estás tratando de hacer FP, simplemente utiliza un lenguaje funcional". Trabajo en un entorno incrustado que necesita vincularse a muchas otras bibliotecas C, y no tiene mucho espacio para muchas bibliotecas compartidas más grandes y no admite muchos tiempos de ejecución de idiomas. Además, la asignación de memoria dinámica está fuera de discusión. También soy realmente curioso.

Muchos de nosotros hemos visto este macro ingenioso C para las expresiones lambda:

#define lambda(return_type, function_body) \ 
({ \ 
     return_type __fn__ function_body \ 
      __fn__; \ 
}) 

Y un ejemplo de uso es:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; }); 
max(4, 5); // Example 

Usando gcc -std=c89 -E test.c, la lambda se expande a:

int (*max)(int, int) = ({ int __fn__ (int x, int y) { return x > y ? x : y; } __fn__; }); 

Entonces, estas son mis preguntas:

  1. ¿Qué hace exactamente la línea int (* X); declarar? Por supuesto, int * X; es un puntero a un número entero, pero ¿cómo difieren estos dos?

  2. Echando un vistazo a la macro expuesta, ¿qué diablos hace el __fn__ final? Si escribo una función de prueba void test() { printf("hello"); } test; - eso arroja inmediatamente un error. No entiendo esa sintaxis.

  3. ¿Qué significa esto para la depuración? (Estoy planeando experimentar con esto y con gdb, pero las experiencias u opiniones de otros serían geniales). ¿Esto arruinaría los analizadores estáticos?

+3

Esto no es ANSI C. –

+0

Además, no veo 'int (* X);' en ninguna parte. –

+0

No está allí como tal. Al tratar de descifrar la sintaxis, estaba un poco perplejo que int (* X); compila, pero no está muy seguro de lo que definió. –

Respuesta

31

Esta declaración (al ámbito de bloque):

int (*max)(int, int) = 
    ({ 
    int __fn__ (int x, int y) { return x > y ? x : y; } 
    __fn__; 
    }); 

no es C, pero es válido GNU C.

Se hace uso de dos gcc extensiones:

  1. nested functions
  2. statement expressions

Ambos funciones anidadas (definiendo una función dentro de una instrucción compuesta) y declaración expresiones (({}) , básicamente un bloque que produce un valor) no están permitidos en C y com e de GNU C.

En una expresión de declaración, la última declaración de expresión es el valor de la construcción. Esta es la razón por la cual la función anidada __fn__ aparece como una declaración de expresión al final de la expresión de la declaración. Un designador de función (__fn__ en la última declaración de expresión) en una expresión se convierte en un puntero a una función por las conversiones habituales. Este es el valor utilizado para inicializar el puntero de función max.

+3

Tal funcionalidad útil, ¿Cuándo se implementará esto en ANSI C? – Pacerier

+1

Es sintácticamente válido GNU C, pero ¿es realmente válido desde el punto de vista semántico? Cuando sale el bloque que contiene la definición de la función (que está antes de que pueda producirse el uso real de la "expresión lambda"), ¿no se invalidaría la función trampoline junto con ella? – Dolda2000

+0

@ Dolda2000 Los datos creados en una expresión de declaración probablemente tengan una duración estática. Después de todo, ya no es ANSI C. Es GNU C. GNU define ese estándar. –

0
  1. int (*max)(int, int) es el tipo de variable que se está declarando. Se define como un puntero de función llamado max que devuelve int, y toma dos ints como parámetros.

  2. __fn__ se refiere al nombre de la función, que en este caso es max.

  3. No tengo una respuesta allí. Me imagino que puede recorrerlo si lo ha ejecutado a través del preprocesador.

+0

wrt 2, simplemente no entiendo por qué 'int (* max) (int, int) = ({int __fn__ (int x, int y) {return x> y? x : y;} __fn__;}); 'compilar, pero' void test() {puts ("hello"); } test; '* not * compile? –

+0

@ B.VB. Haría que el código se vea exactamente igual. 'void (* test)() = ({void test() {puts (" hello ");} test;});' o si eso no funciona, 'void (* test)() = ({ void __fn __() {puts ("hello");}} __fn__;}); ' – gcochard

+0

' __fn__' es simplemente un nombre arbitrario para la función definida dentro del bloque. El nombramiento es lamentablemente confuso. – FooF

0

respuesta parcial: No se int (* X) está interesado en que es int (* X) (Y, Z).. Ese es un puntero a la función llamada X que toma (y, z) y devuelve int.

Para la depuración, esto será realmente difícil. La mayoría de los depuradores no pueden rastrear a través de una macro. Lo más probable es que tenga que depurar el ensamblaje.

5

Su macro lambda explota dos funciones funky. Primero utiliza funciones anidadas para definir el cuerpo de su función (por lo tanto, su lambda no es realmente anónima, solo usa una variable implícita __fn__ (que debe ser renombrada a otra cosa, ya que los nombres de doble guión bajo están reservados para el compilador, por lo que tal vez algo como yourapp__fn__ sería mejor)

Todo esto está a su vez lleva a cabo dentro de una sentencia compuesta GCC (ver http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs), el formato básico de los cuales más o menos así:.

({ ...; retval; }) 

la última declaración de la declaración compuesta siendo la dirección de la función recién declarada. Ahora, int (*max)(int,int) simplemente se le asigna el valor de la instrucción compuesta, que ahora es el puntero a la función 'anónimo' recién declarada.

Las macros de depuración son un dolor real, por supuesto.

En cuanto a la razón por la cual test; .. al menos aquí, obtengo la 'prueba redeclarada como un tipo diferente de símbolo', supongo que significa que GCC lo está tratando como una declaración y no como una expresión (inútil). Debido a que las variables sin tipo son predeterminadas a int y porque ya ha declarado test como una función (esencialmente, void (*)(void)) lo obtiene ... pero podría estar equivocado al respecto.

Sin embargo, esto no es portátil de ninguna manera.

+0

+1 para sugerir el cambio de nombre de '__fn__'. – FooF

Cuestiones relacionadas