Mastering declarations in C - Part 1

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?

Laughs in C!

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 -

  1. Type specifiers: void, char, short, int, long, signed, unsigned, float, double etc. and struct, union or enum specifiers.
  2. Storage class: extern, auto, static, register, typedef
  3. 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 -

  1. A function can't return a function. So f()() is not allowed.
  2. A function can't return array. So f()[] is not allowed.
  3. 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 -

  1. Start reading the declaration from the name and follow the order of precedence.
  2. 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
  3. If const and/or volatile 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 -

  1. We start with the name next and get next 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[])())()
  1. 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!

Aniket Bhattacharyea

Aniket Bhattacharyea