Funciones anónimas que utilizan expresiones de declaración GCC

6 minutos de lectura

avatar de usuario
Factura VB

Esta pregunta no es muy específica; es realmente para mi propio enriquecimiento de C y espero que otros también puedan encontrarlo útil.

Descargo de responsabilidad: sé que muchos tendrán el impulso de responder con “si estás tratando de hacer FP, entonces usa un lenguaje funcional”. Trabajo en un entorno integrado que necesita vincularse a muchas otras bibliotecas de C, y no tiene mucho espacio para muchas bibliotecas compartidas más grandes y no admite muchos tiempos de ejecución de lenguaje. Además, la asignación de memoria dinámica está fuera de cuestión. También tengo mucha curiosidad.

Muchos de nosotros hemos visto esta ingeniosa macro C para 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

Utilizando gcc -std=c89 -E test.cla 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, entero * X; es un puntero a un número entero, pero ¿en qué se diferencian estos dos?

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

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

  • ademas no veo int (*X); en cualquier sitio.

    –Oliver Charlesworth

    1 de mayo de 2012 a las 22:49

  • No está ahí como tal. Mientras trataba de descifrar la sintaxis, estaba un poco perplejo porque int (*X); compila, pero no estoy realmente seguro de lo que definió …

    – Factura VB

    1 mayo 2012 a las 22:51

  • int (*X)(<arguments>); es un prototipo de un puntero de función con el nombre de X. En otras palabras: X es un puntero a una función que acepta <arguments> y devolviendo un int.

    – pmg

    1 mayo 2012 a las 22:52


  • Entonces, digamos que solo tenemos int (*x) – esto es un puntero a una función con… una lista variable de argumentos, ¿sin argumentos?

    – Factura VB

    1 de mayo de 2012 a las 22:54

  • Para hacer de gcc un compilador C89 (o C99) estricto, use -std=c89 -pedantic.

    – pmg

    1 de mayo de 2012 a las 22:56

avatar de usuario
ouah

Esta declaración (en el alcance del bloque):

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

no es C pero es GNU C válido.

Hace uso de dos gcc extensiones:

  1. funciones anidadas
  2. expresiones de declaración

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

En una expresión de sentencia, la última sentencia de expresión es el valor de la construcción. Por eso la función anidada __fn__ aparece como una instrucción de expresión al final de la expresión de instrucció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 mediante las conversiones habituales. Este es el valor utilizado para inicializar el puntero de función. max.

  • Una funcionalidad tan útil, ¿cuándo se implementará en ANSI C?

    – Pacerier

    6 de marzo de 2015 a las 11:03


  • Es GNU C sintácticamente válido, pero ¿es realmente válido semánticamente? Cuando el bloque que contiene la definición de la función sale (que es antes de que pueda ocurrir cualquier uso real de la “expresión lambda”), ¿no se invalidaría la función trampolín junto con él?

    – Dolda2000

    9 ago 2016 a las 16:37


  • @Dolda2000 Los datos creados en una expresión de instrucció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.

    – Braden Mejor

    4 de febrero de 2017 a las 12:22

  • @BradenBest: ciertamente no tiene una duración estática, porque un puntero a una función anidada no es válido después de que regresa la función contenedora. Dado que una función anidada tiene acceso a todas las variables que están léxicamente en su alcance, no sería extraño que su trampolín se asigne con la misma duración que esas variables. Sin embargo, el manual es un poco confuso sobre el tema.

    – Dolda2000

    10 de febrero de 2017 a las 3:27

  • La expresión de declaración @Pacerier es compatible con gcc y clang, por lo que es bastante portátil y puede usarla de manera segura (incluso el código del kernel de Linux la usa)

    – SwiftMango

    23 de noviembre de 2017 a las 15:57


Su macro lambda explota dos características originales. Primero, usa funciones anidadas para realmente definir el cuerpo de su función (por lo que su lambda no es realmente anónimo, solo usa un implícito __fn__ variable (que debe cambiarse de nombre a otra cosa, ya que los nombres de doble guión bajo inicial están reservados para el compilador, por lo que tal vez algo como yourapp__fn__ seria mejor).

Todo esto se realiza en sí mismo dentro de una declaración compuesta GCC (ver http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs), cuyo formato básico es algo así como:

({ ...; retval; })

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

La depuración de macros es 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’, lo que supongo que significa que GCC lo trata como una declaración y no como una expresión (inútil). Debido a que las variables sin tipo por defecto int y porque ya has declarado test como una función (esencialmente, void (*)(void)) entiendes eso… pero podría estar equivocado al respecto.

Sin embargo, esto no es portátil por ningún tramo de la imaginación.

Respuesta parcial: no es int(*X) lo que le interesa. Es int (*X)(y,z). Esa es una función que apunta a la función llamada X que toma (y, z) y devuelve int.

Para la depuración, esto será muy 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.

  1. int (*max)(int, int) es el tipo de variable que está declarando. Se define como un puntero de función llamado max que devuelve int y toma dos int 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 atravesarlo si lo ha ejecutado a través del preprocesador.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad