How to interpret complex C/C++ declarations

Ever came across a declaration like int * (* (*fp1) (int) ) [10]; or something similar that you couldn't fathom? This article will teach you to interpret such complex C/C++ declarations, including the use of typedef, const, and function pointers.

Ever came across a declaration like int * (* (*fp1) (int) ) [10]; or something similar that you couldn't fathom? This article will teach you to interpret C/C++ declarations, starting from mundane ones (please bear with me here) and moving on to very complex ones. We shall see examples of declarations that we come across in everyday life, then move on to the troublesome const modifier and typedef, conquer function pointers, and finally see the right-left rule, which will allow you to interpret any C/C++ declaration accurately. I would like to emphasize that it is not considered good practice to write messy code like this; I'm merely teaching you how to understand such declarations. Note: This article is best viewed with a minimum resolution of 1024x768, in order to ensure the comments don't run off into the next line.

Coming to the declaration of a pointer variable, it would be declared as something like:

int *p;

This is to be interpreted as "declare p as an int * i.e., as a pointer to an int". I'll need to make a small note here - it is always better to write a pointer (or reference) declaration with the * (or &) preceding the variable rather than following the base type. This is to ensure there are no slip-ups when making declarations like:

int* p,q;

At first sight, it looks like p and q have been declared to be of type int *, but actually, it is only p that is a pointer, q is a simple int.

We can have a pointer to a pointer, which can be declared as:

char **argv;

In principle, there is no limit to this, which means you can have a pointer to a pointer to a pointer to a pointer to a float, and so on.

Consider the declarations:

int RollNum[30][4];
int (*p)[4]=RollNum;
int *q[5];

Here, p is declared as a pointer to an array of 4 ints, while q is declared as an array of 5 pointers to integers.

We can have a mixed bag of *s and &s in a single declaration, as explained below:

int **p1; // p1 is a pointer to a pointer to an int.
int *&p2; // p2 is a reference to a pointer to an int.
int &*p3; // ERROR: Pointer to a reference is illegal.
int &&p4; // ERROR: Reference to a reference is illegal.

The const keyword is used when you want to prevent a variable (oops, that's an oxymoron) from being modified. When you declare a const variable, you need to initialize it, because you can't give it a value at any other time.

constint n=5;
intconst m=10;

The two variables n and m above are both of the same type - constant integers. This is because the C++ standard states that the const keyword can be placed before the type or the variable name. Personally, I prefer using the former style, since it makes the const modifier stand out more clearly.

const is a bit more confusing when it comes to dealing with pointers. For instance, consider the two variables p and q in the declaration below:

constint *p;
intconst *q;

Which of them is a pointer to a const int, and which is a const pointer to an int? Actually, they're both pointers to const ints. A const pointer to an int would be declared as:

int * const r= &n; // n has been declared as an int

Here, p and q are pointers to a const int, which means that you can't change the value of *p. r is a const pointer, which means that once declared as above, an assignment like r=&m; would be illegal (where m is another int) but the value of *r can be changed.

To combine these two declarations to declare a const pointer to a const int, you would have to declare it as:

constint * const p=&n // n has been declared as const int

The following declarations should clear up any doubts over how const is to be interpreted. Please note that some of the declarations will NOT compile as such unless they are assigned values during declaration itself. I have omitted them for clarity, and besides, adding that will require another two lines of code for each example.

typedef allows you a way to overcome the *-applies-to-variable-not-type rule. If you use a typedef like:

typedefchar * PCHAR;
PCHAR p,q;

both p and q become pointers. If the typedef had not been used, q would be a char, which is counter-intuitive.

Here are a few declarations made using typedef, along with the explanation:

typedefchar * a; // a is a pointer to a char
typedef a b(); // b is a function that returns
// a pointer to a char
typedef b *c; // c is a pointer to a function
// that returns a pointer to a char
typedef c d(); // d is a function returning
// a pointer to a function
// that returns a pointer to a char
typedef d *e; // e is a pointer to a function
// returning a pointer to a
// function that returns a
// pointer to a char
e var[10]; // var is an array of 10 pointers to
// functions returning pointers to
// functions returning pointers to chars.

typedefs are usually used with structure declarations as shown below. The following structure declaration allows you to omit the struct keyword when you create structure variables even in C, as is normally done in C++.

Function pointers are probably the greatest source of confusion when it comes to interpreting declarations. Function pointers were used in the old DOS days for writing TSRs; in the Win32 world and X-Windows, they are used in callback functions. There are lots of other places where function pointers are used: virtual function tables, some templates in STL, and Win NT/2K/XP system services. Let's see a simple example of a function pointer:

int (*p)(char);

This declares p as a pointer to a function that takes a char argument and returns an int.

A pointer to a function that takes two floats and returns a pointer to a pointer to a char would be declared as:

char ** (*p)(float, float);

How about an array of 5 pointers to functions that receive two const pointers to chars and return a void pointer?

This is a simple rule that allows you to interpret any declaration. It runs as follows:

Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.

One small change to the right-left rule: When you start reading the declaration for the first time, you have to start from the identifier, and not the innermost parentheses.

Take the example given in the introduction:

int * (* (*fp1) (int) ) [10];

This can be interpreted as follows:

Start from the variable name -------------------------- fp1

Nothing to right but ) so go left to find * -------------- is a pointer

Jump out of parentheses and encounter (int) --------- to a function that takes an int as argument

float ( * ( *b()) [] )(); // b is a function that returns a
// pointer to an array of pointers
// to functions returning floats.
void * ( *c) ( char, int (*)()); // c is a pointer to a function that takes
// two parameters:
// a char and a pointer to a
// function that takes no
// parameters and returns
// an int
// and returns a pointer to void.
void ** (*d) (int &,
char **(*)(char *, char **)); // d is a pointer to a function that takes
// two parameters:
// a reference to an int and a pointer
// to a function that takes two parameters:
// a pointer to a char and a pointer
// to a pointer to a char
// and returns a pointer to a pointer
// to a char
// and returns a pointer to a pointer to void
float ( * ( * e[10])
(int &) ) [5]; // e is an array of 10 pointers to
// functions that take a single
// reference to an int as an argument
// and return pointers to
// an array of 5 floats.

I got the idea for this article after reading a thread posted by Jörgen Sigvardsson about a pointer declaration that he got in a mail, which has been reproduced in the introduction. Some of the examples were taken from the book "Test your C skills" by Yashvant Kanetkar. Some examples of function pointers were given by my cousin Madhukar M Rao. The idea of adding examples with mixed *s and &s and typedef with structs was given by my cousin Rajesh Ramachandran. Chris Hills came up with modifications to the right-left rule and the way in which some examples were interpreted.

Share

About the Author

Vikram is a 20-something bloke working in Madras (aka Chennai), India. Vikram was born in Madras, brought up in Coimbatore, and is now back in Madras. He loves listening to music, reading and watching cricket. He hates cats.

Vikram joined CP way back in 2002 when he was in college and had papers on Windows programming. In his past lives, he languished around, roaming the wilderness of Solaris, eventually moving on to writing software for managing ATMs in .NET. He now works as a Business Analyst who dabbles in SQL for an investment bank, working with applications for structured products.

He "loves everyone" for whatever that is worth. And he rather makes a big deal out of the fact that he's ambidextrous....

The variable data is being casted to GtkWidget** (which is a pointer to pointer to a GtkWidget datatype - whatever it is), and then the value at that location (the whole thing is a pointer, which could be pointing to some memory location) is initialised to NULL.

I've never ever worked anywhere where there has not been someone who given the choice I would not work with again. It's a job, you do your work, put up with the people you don't like, accept there are probably people there that don't like you a lot, and look forward to the weekends.
- Josh Gray.

I already had a concept in pointers, but always fumbled when I encountered some complex long pointer declarations ? How to decipher them ? Thanks to your article now I think with a little bit of practice I will be well equipped to tackle question of such kind.

I will practice on this a bit , and come back here to post if I have any query , thank you for this outstanding article.
Regards
MiniGWeek

compilation of the program is done. but i am getting an warning as "suspitious pointer convertion". i could not understand why. please clear my doubt. i am trying to get the input for a two dimentional array thru an array of pointers and return the pointer to the two dimentional array of pointers (point) to main and display the result there. but the warning is in the line "point=fun();" in the main and "return point" in the function.why it is so?

Thanks for the kind words, Akram. It's been a long while since I wrote this.

Coming to your question, the function parameters will have to be read using the same right-left rule, with the constraint that they're bounded by commas (between the parameters), a left parenthesis (for the first parameter), and a right parenthesis (for the last parameter). At least, that's what I think... I haven't done any C++ in years now.

In case you're asking seriously, no, I didn't write the article. I didn't link to it because I've never read it before you gave me the link. I got the idea from reading a thread in the Lounge, like it says in the article.

Good article - reminds me of the good ol days of working in C on Unix platform...still am fond of C even though I moved over to C#...just a fyi, there's another way of illustrating the deciphering of complex declarations you know what a snail's shell looks like, starts on the inside and spiral outwards, apply that here (write it down first), starting with the inside-most variable name and draw a spiral going thru the parenthesis and continue in spiral outward fashion going thru the */[]/int/const/ etc and as you stop at each part of the declaration write it down on separate sheet.... e.g.

const int *p[6];
start at p - write down 'p is a variable'
draw the outward spiral until you come to [] and write down 'array of ...'
keep going thru *, int and const.... in spiral outward fashion!

Check out The Design & Evolution of C++ as Stroustrup touches on this messy notation and some ideas he had for simplifying it. Unfortunately he never put his suggestions into C++ because it would break C compatibility.

How about adding the notation for a pointer to a class method:

int (CFoo::*p)();

p is a pointer to a method in class CFoo that takes no params and returns an int.

There is also the matter of where the calling convention goes:

int (__stdcall* q)();

Occasionally you'll see the function pointer notation without a name in a function prototype:

int Func( int(*)(char) );

This is a prototype where the parameter of Func() is a function pointer. The "(*)" part can be confusing if you forget that parameters in prototypes don't need to be named.
[edit]Ah, I see you did cover this, it was just buried in a complex prototype and my eyes glazed over trying to read that part [/edit]

Children's jokes have more to do with breaking wind, defecation and human bottoms. But, Art Buchwald concludes, they are revolting enough to turn the stomachs of adults. Truly the grown-up is more in need of protection from the child pornographer than the other way round. Another instance of the child being the father of the man, what?

Children's jokes have more to do with breaking wind, defecation and human bottoms. But, Art Buchwald concludes, they are revolting enough to turn the stomachs of adults. Truly the grown-up is more in need of protection from the child pornographer than the other way round. Another instance of the child being the father of the man, what?

I liked the article. Another way, to interprete the Right-Left rule is to imagine expanding spirals starting from the variable, going left first, then going right all the while expanding until the whole declaration is understood. I read this in one of the books and find it very useful.

This is a good tutorial. I especially liked the right to left rule.
However, with that said:
My advice would be: keep it simple, even if you know how to do all the complicated stuff.. (thats what I do). With today's Macros and higher level languages such as C# and JAVA, programmers should concern themselves more with writing good solid reliable and maintainable code rather than with fancy declaration that show off the writer's C/C++ skills.

I mean, really if you need to have functions that return pointers to functions that return pointers to array of functions that return void, you should really rethink your design...

> I mean, really if you need to have functions that return
> pointers to functions that return pointers to array of
> functions that return void, you should really rethink your
> design...
Point taken. But...

> keep it simple, even if you know how to do all the
> complicated stuff....
... I thought I made it pretty clear in the introduction.

Yes indeed, I can recall those times when I was just learning C++, and the many body blows I took just trying to see the light on pointers. (I would say, it took me the better part of a year before I was finally able to stand my ground and look that cruel beast right in the eye.) Now I use them in ways that would've been unimaginable for me back then.

Since your article is something of a primer for beginners, it would be of tremendous value for them (I believe) if you could include a couple of samples for each of the examples you've shown (in the interest and firsthand memory I know I would have valued when I was dealing with that part of the language.)

I really like your right-left rule, it’s at the very centre of how declarations work.

It also works with const pointers. Up to now I’ve never been able to remember whether const binds with the * on its left or its right. With this rule it just works! In int const *q;, q is a pointer to a const int. In int * const r, r is a const pointer to an int.

I’d like to suggest some improvements to the rule.

Firstly, it’s not always clear what the innermost parentheses are. In your example of void ** (*d)(int &, char **(*)(char *, char **)), it looks as though they should be char **(*), but in fact they’re void ** (*d). So you need to start at the variable or type being declared (or where that would appear, in casts and some other places it’s omitted).

“go right and then go left” really means go right until you hit the next enclosing right parenthesis, at which point go left until you hit the corresponding left parenthesis.

Finally, you haven’t mentioned how to deal with a function’s parameter list. Each parameter is interpreted according to the right-left rule (a nice example of recursion there).

Your example of void *(*c)(char, int (*)()); now becomes:

c is a pointer to
a function taking 2 parameters: a char, and
a pointer to
a function taking no parameters
and returning an int;
and returning a pointer to a void.