¿Cuál es el significado de este fragmento de código? void (*señal(int sig, void (*func)(int)))(int);

10 minutos de lectura

avatar de usuario
fotón

Me encontré con este fragmento de código y me perdí por completo al interpretar su significado.

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

¿Qué es una explicación detallada para el código en la línea 2?

Yo sé eso void y int son tipos, *func es un puntero para una función y los corchetes son para prioridad. Pero todavía no obtengo la (*señal …), la (int) y todo combinado. Cuanto más detallado mejor.

Probablemente he conocido el significado/efecto de esta declaración. Pero tuve que hacer algunas pruebas más para ayudarme a entender lo que está pasando, como se muestra a continuación:

  1 #include <signal.h>
  2 void (*signal)(int sig, void (*func)(int));
  3 void (*signal)(int);  // then void (signal)(int) again.
  4 //void (*signal(int sig, void (*func)(int)))(int); //break this line into two lines above
  5
  6 int main(){}

En el código anterior, rompí void (*signal(int sig, void (*func)(int)))(int) en dos líneas. Para la línea 3, probé ambos void (*signal)(int) y void (signal)(int)con el mismo resultado de error que indicaba que estaba tratando de volver a declarar signal:

TestDeclaration.c:2: error: ‘señal’ redeclarada como un tipo diferente de símbolo /usr/include/signal.h:93: error: la declaración anterior de ‘señal’ estaba aquí
TestDeclaration.c:3: error: ‘señal’ redeclarada como un tipo diferente de símbolo /usr/include/signal.h:93: error: la declaración anterior de ‘señal’ estaba aquí

Ahora sé que ambos juicios son formas incorrectas de declaración, pero ¿por qué son incorrectas? ¿Por qué la forma original de declaración NO es una redeclaración?

  • +1 por demostrar que realmente entiendes algunos de ello en oposición a ninguna de eso

    – BoltClock

    14 de septiembre de 2010 a las 7:02

  • Tratar cdecl.org

    –Björn Pollex

    14 de septiembre de 2010 a las 7:13

  • Curiosamente, cdecl.org da un error de sintaxis en este caso. ¿Alguien puede explicar esto?

    –Björn Pollex

    14 de septiembre de 2010 a las 7:22

  • @Space Cowboy: Da mi error de sintaxis para este…

    – Cédric H.

    14 de septiembre de 2010 a las 7:23

  • @Space_C0wb0y: funciona si elimina los nombres de los parámetros.

    –CB Bailey

    14 de septiembre de 2010 a las 7:25

avatar de usuario
CB Bailey

Es la declaración de una función que toma un int y un puntero a una función (tomando int devolviendo void) y devolviendo un puntero a una función (tomando int y regreso vacío).


Explicación o guía para la interpretación.

Puede interpretar tratando todo lo que está entre paréntesis como una sola entidad y luego trabajando hacia adentro usando la regla “la declaración sigue al uso”.

vacío (*señal(int sig, void (*func)(int)))(En t);

La entidad entre paréntesis parece una función que toma int y regresando void.

Quitando la parte exterior:

*signal(int sig, void (*func)(int))

Entonces, signal toma algunos parámetros y devuelve algo que puede ser desreferenciado (debido a la *) para formar una función tomando int y regresando void.

Esto significa signal es una función que devuelve un puntero a una función (tomando int y regresando void).

Mirando los parámetros se necesita un int (es decir sig) y void (*func)(int) que es un puntero a una función (tomando int y regresando void).

  • +1. muy conciso Es útil conocer la regla en el sentido de las agujas del reloj/espiral cuando se intenta analizar declaraciones C complejas. – c-faq.com/decl/spiral.anderson.html

    – Noufal Ibrahim

    14 de septiembre de 2010 a las 7:09

  • Personalmente, no soy fanático de la regla de las agujas del reloj/espiral. Prefiero trabajar de afuera hacia adentro. Sé que no es un enfoque popular, pero estoy mucho más feliz de que estoy aplicando las reglas gramaticales correctamente con mi enfoque.

    –CB Bailey

    14 de septiembre de 2010 a las 7:18

  • Es gracioso que la página de reglas en el sentido de las agujas del reloj/espiral vinculada anteriormente incluya la misma declaración sobre la que pregunta el OP.

    – Jon Purdy

    15 de septiembre de 2010 a las 0:58

  • Hay una respuesta aún mejor en un duplicado de esta pregunta: stackoverflow.com/a/9501054/220060

    – finalmente

    11 de agosto de 2012 a las 14:14

avatar de usuario
Bart van Ingen Schenau

Este es uno de los ejemplos clásicos de lo intrincadas que pueden llegar a ser las declaraciones de C.
Para entender esta declaración, suele ser útil introducir un typedef:

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);

El typedef declara un puntero a una función (toma un parámetro int y no devuelve nada). La función signal ahora se puede ver como una función que toma dos parámetros (un int y un puntero a una función) y devuelve un puntero a una función.

Esto también se puede derivar de la declaración original, pero requiere un poco de práctica. La forma habitual es comenzar en el identificador que nombra la entidad más externa (signal es este caso):

signal es un …

Luego, lee a la derecha hasta que encuentre un paréntesis de cierre sin igual o el final de la declaración: void (*signal(int sig, void (*func)(int))(int)

signal es una función tomando… devolviendo…

Ahora puede elegir entre analizar primero los parámetros o el valor devuelto primero. Haré el valor de retorno primero. Para eso, lee hacia atrás para encontrar el paréntesis abierto correspondiente: void (signal( / ... */ ))(int)

`signal es una función que toma… devuelve un puntero a…

Al leer de un lado a otro de esta manera se obtiene en etapas sucesivas:

`signal es una función que toma… devuelve un puntero a (función que toma… devuelve…)

`signal es una función que toma… devuelve un puntero a (función que toma… devuelve void)

`signal es una función que toma… devuelve un puntero a (función que toma un int y devuelve void)

`signal es una función que toma dos parámetros: (un int) y (un puntero a una función que toma un int y devuelve void), y devuelve un puntero a una (función que toma un int y devuelve void)

avatar de usuario
keshlam

Un mnemotécnico que creé hace muchos años, que es invaluable cuando se trata de entender tipos complicados:

Remember these rules for C declares
And precedence never will be in doubt
Start with the Suffix, Proceed with the Prefix
And read both sets from the inside, out.

Excepto donde los paréntesis cambian esa precedencia, por supuesto.

Aplicándolo a este caso:

void (*signal(int sig, void (*func)(int)))(int);

signal is:
  [inside parentheses]
  [suffix ()] a function, whose arguments are
    sig, which is [prefix int] an integer, and
      func, which is:
         [inside parentheses]
           [no suffix within these parens]
           [prefix *] a pointer to
         [suffix ()] a function, whose argument is
           an int
         [no more suffixes]
         [prefix void] and which returns void
         [no more prefixes]
       [no more arguments]
     [prefix *] And which returns a pointer to
     [no more prefixes within these parens]
   [suffix ()] a function, whose argument is
      an int
   [no more suffixes]
   [prefix void] and which returns void.

Con un poco de práctica, llegará al punto en que podrá hacer todo eso sobre la marcha:

"Signal is function, whose arguments are:
    sig, an integer,
    and func, a pointer to a function whose argument is an int and which returns void
... which returns a pointer to a function that takes int as an argument and returns void.

(Perdón por el error a la primera, no tengo práctica).

Sí, ese mnemotécnico (con el implícito “excepto los paréntesis, por supuesto) funciona para todas las declaraciones de C, sin importar cuán mal se entremezclen los punteros, las matrices y las funciones.

Esta es una habilidad REALMENTE útil cuando se trata de descubrir cómo funciona el código de otra persona… o incluso de descubrir algo propio que no ha visto en mucho tiempo.

Pero, sí, la mejor manera de manejar cualquier cosa que no creas que la gente pueda leer de un vistazo es construirla en capas con typedefs. Es probable que los tipos de componentes sean útiles en sí mismos, y dar un paso a la vez evita que las personas se pierdan tratando de averiguar qué paréntesis coincide con cuál. ¡Sé amable con la próxima persona que toque tu código!

Si encuentra útil el mnemotécnico, siéntase libre de citarlo en otro lugar; solo deme crédito como su autor, por favor.

Por cierto, también hay herramientas de “Explicador de C” que analizarán las acciones de C y harán la conversión a la descripción en inglés por usted. El mío se llamaba CEX, por razones obvias, pero existen muchos otros y deberías poder encontrar uno si no quieres comprometer esta habilidad con el software húmedo o si alguien te entrega algo que es demasiado feo para que puedas seguirle la pista.

  • Y sí, el hecho de que Inicio y Sufijo, y Proceder y Prefijo, compartan consonantes iniciales es deliberado; que le ayuda a recordar el orden correcto.

    – keshlam

    1 de enero de 2014 a las 18:28


avatar de usuario
draganicimw

Tomemos un ejemplo de cómo se podría usar esta desagradable declaración:

void (*signal(int sig, void (*func)(int)))(int);

Sin demasiada verbosidad, podríamos decir que “señal” es una función con dos parámetros que devuelve una función.

#include <stdio.h>

// First function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_red(int color)
{
    printf("[car_is_red] Color %d (red) is my favorite color too !\n", color);
}

// Second function that could be returned by our principal function
// Note that we can point to it using void (*f)(int)
void car_is_gray(int color)
{
    printf("[car_is_gray] I don't like the color %d (gray) either !\n", color);
}

// The function taken as second parameter by our principal function
// Note that we can point to it using void (*func)(int)
void show_car_type(int mod)
{
    printf("[show_car_type] Our car has the type: %d\n",mod);
}

/* Our principal function. Takes two parameters, returns a function. */
void (* show_car_attributes(int color, void (*func)(int)) )(int)
{
    printf("[show_car_attributes] Our car has the color: %d\n",color); // Use the first parameter

    int mod = 11;  // Some local variable of our function show_car_attributes()
    func(mod);  // Call the function pointed by the second parameter (i.e. show_car_type() )

    // Depending on color value, return the pointer to one of two functions
    // Note that we do NOT use braces with function names
    if (color == 1)
        return car_is_red;
    else
        return car_is_gray;
    }


//main() function
int main()
{
    int color = 2;   // Declare our color for the car
    void (*f)(int);  // Declare a pointer to a function with one parameter (int)

    f = show_car_attributes(color, show_car_type); // f will take the return 
           // value of our principal function. Stated without braces, the 
           // parameter  "show_car_types" is a function pointer of type 
           // void (*func)(int).

    f(color);  // Call function that was returned by show_car_attributes()

    return 0;
}

Veamos qué saldrá:

Si color = 1

[show_car_attributes] Our car has the color: 1
[show_car_type] Our car has the type: 11
[car_is_red] Color 1 (red) is my favorite color too !

Si color = 2

[show_car_attributes] Our car has the color: 2
[show_car_type] Our car has the type: 11
[car_is_gray] I don't like the color 2 (gray) either !

avatar de usuario
Vijay

Puntero de retorno a una función que toma un:

  • entero como primer argumento argumento y
  • un puntero a una función (que toma un int y devuelve void) como argumento como segundo argumento.

Y toma un argumento entero.

¿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