janekb04
janekb04

Reputation: 4915

A way to efficiently parse function pointer declaration syntax

So, until now, I was pretty sure my mental pointer-to-function parser was able to parse even the toughest of pointers... how wrong was I! While reading some legacy code I found this:

void (*(*somename)(void (*)()))(void (*)());

Apparently, it means declare somename as pointer to function (pointer to function returning void) returning pointer to function (pointer to function returning void) returning void (according to http://cdecl.org, at least).

It seems that I oversimplified the way function pointer declarations work. I was pretty sure the syntax is return-type(*variable-name)(argument-types...). It works for a lot of cases, but not for complex ones, like above. How could I go about reading such unordinary and complex declarations, without having to think about all the grammar rules and trying to figure out if I should read from left to right or in reverse or in some other weird way?

Upvotes: 0

Views: 753

Answers (4)

John Bode
John Bode

Reputation: 123458

The method I've developed is to start with the leftmost identifier and work out, keeping in mind the following precedence rules:

T *a[N];   // a is an array of pointer
T (*a)[N]; // a is a pointer to an array
T *f();    // f is a function returning a pointer
T (*f)();  // if is a pointer to a function

and doing that recursively for any function parameters.

I'm going to use λ to represent unnamed parameters, so we get something like this:

         somename                               -- somename is
        *somename                               -- a pointer to
       (*somename)(           )                 --   a function taking
       (*somename)(       λ   )                 --     unnamed parameter is
       (*somename)(      *λ   )                 --     a pointer to
       (*somename)(     (*λ)())                 --       a function taking unspecified parameters
       (*somename)(void (*λ)())                 --       returning void
      *(*somename)(void (*λ)())                 --   returning a pointer to
     (*(*somename)(void (*λ)()))(           )   --     a function taking
     (*(*somename)(void (*λ)()))(       λ   )   --       unnamed parameter is
     (*(*somename)(void (*λ)()))(      *λ   )   --       a pointer to
     (*(*somename)(void (*λ)()))(     (*λ)())   --         a function taking unspecified parameters
     (*(*somename)(void (*λ)()))(void (*λ)())   --         returning void
void (*(*somename)(void (*λ)()))(void (*λ)());  --     returning void

In English, somename is a pointer to a function that takes a pointer to another function as an argument and returns a pointer yet another function that takes a pointer to a still another function as its argument and returns void.

Types this obnoxious are rare in the wild, but they do pop up occasionally.

Upvotes: 1

Purag
Purag

Reputation: 17061

One of my professors taught us how to do this using the "right-left rule." He has documented this here.

Here is how I would apply it to this declaration (start by moving right from the identifier).

void (*(*somename)(void (*)()))(void (*)());
         +-------^                             somename
void (*(*somename)(void (*)()))(void (*)());
        ^--------+                             is pointer
void (*(*somename)(void (*)()))(void (*)());
       ^---------+                             (move left)
void (*(*somename)(void (*)()))(void (*)());
       +----------^                            to function
void (*(*somename)(void (*)()))(void (*)());
       +----------------------^                taking (void (*)())
void (*(*somename)(void (*)()))(void (*)());
      ^-----------------------+                returning pointer
void (*(*somename)(void (*)()))(void (*)());
     ^------------------------+                (move left)
void (*(*somename)(void (*)()))(void (*)());
     +-------------------------^               to function
void (*(*somename)(void (*)()))(void (*)());
     +------------------------------------^    taking (void (*)())
void (*(*somename)(void (*)()))(void (*)());
^-----------------------------------------+    returning void

You can then apply the rule to each argument in the argument lists, starting with whatever's in parenthesis since we don't have identifiers in this case:

void (*)()
      +^      pointer
void (*)()
     ^-+      (move left)
void (*)()
     +--^     to function
void (*)()
^--------+    returning void

Upvotes: 3

Eric Postpischil
Eric Postpischil

Reputation: 222546

The C grammar allows indefinite nesting of various things, so there is no limit on how much memory parsing a declaration may require. Tackling this one:

  • In void (*(*somename)(void (*)()))(void (*)()), we see there are function parameters, so let’s separate them a bit.
  • Proceeding from the right, we can find the match to the rightmost parenthesis and insert spaces for visualization: void (*(*somename)(void (*)())) (void (*)()).
  • So we see this declares (*(*somename)(void (*)())) to be a function returning void and taking a parameter of type void (*)(), which is a pointer to a void function with no prototype.
  • Next, analyze (*(*somename)(void (*)())). The left and right parentheses match, so this is *(*somename)(void (*)()).
  • That is a pointer to the previous thing (a voidfunction taking a pointer to a void function with no prototype).
  • If the rest is simple enough, we may see it is a pointer to a void function with no prototype.

Thus, somename points to a function that:

  • takes a pointer to a void function with no prototype, and
  • returns a pointer to a void function taking a pointer to a void function with no prototype.

If a declaration truly defeats your ability to parse it without aid, one could construct a tree describing it. The C grammar naturally corresponds to a tree, and learning the relevant theory and correspondences between grammar and parsing is a part of a computer science curriculum. This would not be “efficient” for humans as the question asks, but it is a deterministic way to analyze declarations.

Upvotes: 1

Konrad Zapałowicz
Konrad Zapałowicz

Reputation: 281

The trick is to use the clockwise spiral rule http://c-faq.com/decl/spiral.anderson.html A little bit difficult here because of so many parentheses however once you figure it out it should be fine.

Furthermore, you can also do aliasing of a part of the complex declaration with a label and go back to the label when you understand the rest. I mean:

void (*T)(void (*)());

where your T is substitute for (somename)(void ()())

Upvotes: 1

Related Questions