Reputation: 4915
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
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
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
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:
void (*(*somename)(void (*)()))(void (*)())
, we see there are function parameters, so let’s separate them a bit.void (*(*somename)(void (*)())) (void (*)())
.(*(*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.(*(*somename)(void (*)()))
. The left and right parentheses match, so this is *(*somename)(void (*)())
.void
function taking a pointer to a void
function with no prototype).void
function with no prototype.Thus, somename
points to a function that:
void
function with no prototype, andvoid
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
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