简体   繁体   中英

What's the meaning of this piece of code? void (*signal(int sig, void (*func)(int)))(int);

I came across this piece of code and completely got lost interpreting its meaning.

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

What is a detailed explanation for the code at line 2?

I know that void and int are types, the *func is a pointer for a function, and the brackets are for priority. But I still don't get the (*signal ...), the (int), and the whole thing combined together. The more detailed, the better.

Probably I've known the meaning/effect of this declaration. But I had to make some more trials to help me understand what's going on, as below:

  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(){}

In the above code, I broke void (*signal(int sig, void (*func)(int)))(int) into two lines. For line 3, I tried both void (*signal)(int) and void (signal)(int) , with the same error result that indicated that I was trying to redeclare signal :

TestDeclaration.c:2: error: 'signal' redeclared as different kind of symbol /usr/include/signal.h:93: error: previous declaration of 'signal' was here
TestDeclaration.c:3: error: 'signal' redeclared as different kind of symbol /usr/include/signal.h:93: error: previous declaration of 'signal' was here

Now I know both the trials are incorrect ways of declaration, but why are they incorrect? Why is the original way of declaration NOT a redeclaration?

It's the declaration of a function taking an int and a pointer to a function (taking int returning void) and returning a pointer to a function (taking int and returning void).


Explanation, or guide to interpretation

You can interpret by treating everything in parentheses as a single entity and then working inwards using the "declaration follows usage" rule.

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

The entity in the brackets looks like a function taking int and returning void .

Stripping away the outer part:

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

So, signal takes some parameters and returns something that can be dereferenced (due to the leading * ) to form a function taking int and returning void .

This means signal is a function returning a pointer to a function (taking int and returning void ).

Looking at the parameters it takes an int (ie sig ) and void (*func)(int) which is a pointer to a function (taking int and returning void ).

This is one of the classical examples of how convoluted C declarations can become.
To understand this declaration, it usually helps to introduce a typedef:

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

The typedef declares a pointer to a function (taking an int parameter and returning nothing). The function signal can now be seen as a function that takes two parameters (an int and a pointer to a function) and returns a pointer to a function.

This can also be derived from the original declaration, but it takes a bit of practice. The usual way is to start at the identifier that names the outermost entity ( signal is this case):

signal is a ...

Then you read right until you find an unmatched closing parenthesis or the end of the declaration: void (* signal (int sig, void (*func)(int) )(int)

signal is a function taking ... returning ...

Now you can choose between parsing the parameters first, or the return value first. I will do the return value first. For that, you read backwards to find the matching open parenthesis: void ( signal( / ... */ ) ) (int) void ( signal( / ... */ ) ) (int)

`signal is a function taking ... returning a pointer to ...

Reading back and forth this way you get at successive stages:

`signal is a function taking ... returning a pointer to a (function taking ... returning ...)

`signal is a function taking ... returning a pointer to a (function taking ... returning void)

`signal is a function taking ... returning a pointer to a (function taking an int and returning void)

`signal is a function taking two parameters: (an int) and (a pointer to a function taking an int and returning void), and returning a pointer to a (function taking an int and returning void)

A mnemonic I created many years ago, which is invaluable when trying to understand complicated types:

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.

Except where parentheses change that precedence, of course.

Applying it to this case:

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.

With a bit of practice, you'll get to the point where you can do all that on the fly:

"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.

(Sorry about the error first time out -- I'm out of practice.)

Yes, that mnemonic (with the implied "except for parentheses, of course) works for all C declarations, no matter how badly the pointers, arrays, and functions are intermixed.

This is a REALLY useful skill to have when trying to figure out how someone else's code works... or even figuring out something of your own that you haven't seen in a long time.

But, yes, the better way to handle anything that you don't think people will be able to read at a glance is to build it up in layers with typedefs. The component types are likely to be useful themselves, and taking it one step at a time keeps people from getting lost trying to figure out which parenthesis matches which. Be kind to the next person who touches your code!

If you find the mnemonic useful, feel free to quote it elsewhere -- just give me credit as its author, please.

By the way, there are also "C Explainer" tools which will parse C delaractions and do the conversion to English description for you. Mine was called CEX, for obvious reasons, but many others exist and you should be able to find one if you don't want to commit this skill to wetware or if someone hands you something that's really too ugly for you to keep track of.

Let's take an example of how this nasty declaration could be used:

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

Without too much verbosity, we could say that "signal" is a function with two parameters that returns a function.

#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;
}

Let's see what will be output:

If 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 !

If 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 !

Returning pointer to a function which takes an:

  • integer as first argument argument and
  • a pointer to a function (which takes an int and returns void) as an argument as a second argument.

And takes an integer argument.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM