This blog post is adapted from Expert C Programming - Deep C Secrets by Peter Van Der Linden
If you've written C programs before you're already familiar with declarations (probably, sometimes people confuse declarations and definitions, but for the purpose of this blog, I'll assume you know what a declaration is).
You probably feel like it's the easiest part of the language. What could be so difficult about declarations that it requires an entire blog? Our teachers don't spend more than five minutes on this! Like everyone knows
int i;
declares i
as an int
. There is nothing difficult. Well, how about this?
char* const *(*next)();
This is a declaration found in the telnet
program. Can you tell what is this declaring?
Before trying to tackle declarations, we need to know the building blocks of a declaration -
Declarator
The core of the declaration is the declarator. This is made up of the identifier (i. e., the variable name) and optionally any pointer, function brackets and array indices.
int i;
^ declarator
int f(int);
^^^^^^^ declarator
char x[34];
^^^^^ declarator
int a = 3;
^^^^^^ declarator
For convenience, here are the things that can occur from left to right -
Pointers
Zero or more can occur. One of the following -
* const volatile
* volatile
*
* const
* volatile const
Direct declarators
Exactly one can occur. Any one of the following -
identifier
identifier[optional_size]
identifier(arguments...)
(declarator)
Initializer
Zero or one can occur. This is the optional initial value of the variable. Looks like the following -
= initial_value
So putting everything together. you can now detect and extract the declarator from a declaration.
Now a declaration is made up of the following parts from left to right-
Type specifier
At least one. This contains -
- Type specifiers:
void, char, short, int, long, signed, unsigned, float, double
etc. andstruct, union
orenum
specifiers. - Storage class:
extern, auto, static, register, typedef
- Type qualifier:
const, volatile
Not all combinations are valid!!
Declarator
Exactly one as defined previously. It might also be a comma separated list of declarators e. g.
int a, b, *c;
Semicolon
Duh!
One thing to notice is the 4th alternative of direct declarator - (declarator)
. So a declarator contains a direct declarator and a direct declarator can contain another declarator recursively. So, things start to get real complex once we start combining types. But before, there are a few restriction. Some combinations cannot occur -
- A function can't return a function. So
f()()
is not allowed. - A function can't return array. So
f()[]
is not allowed. - An array can't hold a function. So
f[]()
is not allowed.
Now that we have seen the building blocks of declarations, it's time to tackle the declaration itself. Here is the rule you need to remember -
- Start reading the declaration from the name and follow the order of precedence.
- The order of precedence, from high to low is:
1. Parentheses grouping together parts of a declaration.
2. The postfix operators -()
indicating a function and[]
indicating an array.
3. The prefix operator -*
denoting pointer to - If
const
and/orvolatile
is next to a type specifier e. g.int, char
etc. it applies to the type specifier. Otherwise, if it is next to a*
it applies to the pointer.
And that's it. Keeping this in mind, let's try to unravel the example
char* const *(*next)();
Here are the rules we apply with their numbers -
- We start with the name
next
and getnext is ...
2.1 next
is enclosed in a parentheses, so we group together whatever is in the parentheses and get next is a pointer to ...
2. Now we go out of the parentheses and have two choices - the *
on the left or the ()
on the right.
2.2. This rule tells us ()
has higher precedence than *
. So we get next is a pointer to function returning ...
2.3 Now we attach the *
and get next is a pointer to function returning a pointer to ...
3. Finally char * const
is taken as a constant pointer to char
and we get next is a pointer to function returning a pointer to a const pointer-to-char
Just so that we have understood the rule. let's solve the one from the meme -
void (*(*f[])())()
- We start with the name
f is ...
2.1 f
is inside a parentheses, so we group together everything inside the parentheses.
2.2. []
has higher precedence than *
so we get f is an array of ...
2.3. We attach the *
and get f is an array of pointer to ...
2. Now we come out of the parentheses.
2.2 The ()
immediately after the parentheses gets precedence: f is an array of pointer to function returning ...
2.3 Then the *
on the left is attached - f is an array of pointer to function returning pointer to ...
2.3 The ()
on the right gives f is an array of pointer to function returning pointer to function returning ...
3. And finally the void
is attached f is an array of pointer to function returning pointer to function returning void
And it matches with the picture!
And that's it! The secret to master C declarations. I'd suggest you do give a read to the book mentioned at the beginning. You won't regret it!
Still have problems? Head on to cdecl where you can translate from and to c declarations.
In the next part, we'll write a cdecl ourselves!