2. 1
Introduction
C is an immensely powerful programming language. It may appear as both a high-level and a low-
level programming language at the same time. It offers wide range of facilities /options to its
programmer. But, at times, it can get cryptic and its expressions can become mind-boggling. All of us
(i.e., C-programmers), at some time or other, got awe-inspired by the complexity of a C-declaration. In
this paper, I try to decipher some of those much-revered C-declarations and then utilize typedef
keyword of C language to simplify them. Along the way, I would enumerate few simple thumb rules,
which can enable us to read those declarations easily.
Reading Declarations in C
Let us begin with some straightforward declarations, e.g.,
int i; // ‘i’ is an integer
float *f; // ‘f’ is a pointer to a float
We can go beyond the basic types and see some user defined ones.
double *some_func (struct my_struct *st);
/* This is a function declaration. A function called some_func is being declared. It takes a pointer to a
structure of type my_struct and returns a pointer to variable of type double. */
But to really understand these, we need to understand how the compiler sees them. And before
getting into that, I would like to put down some examples, which I am going to use through out this
paper.
1. char * arr [25]; // arr is an array of 25 pointers to char OR is a pointer to an array of 25 chars?
2. long (* ptr_fn) (void);
3. int (*(* cryptic) (long)) [10];
4. float *(*(* r_u_mad) (int)) (double);
3. 2
Thumb Rules
We have come across some thumb rules which makes life easier in understanding C-declarations.
Here they go:
1. First, read the variable that is being declared.
2. With the variable at the center, start spiraling out in anti-clockwise direction.
3. Use the following clues:
a. * means “pointer to”
b. [] means “array of”
c. () means “function taking … and returning …”
d. Read structure, union or enumeration at a single go. E.g., ‘struct st’, ‘union u’ or
‘enum en’.
e. Read adjacent collection of []’s (e.g., [][] - multi-dimensional arrays) all at once.
4. 3
Examples Explained
With these thumb-rules, let us see what the examples have in store for us:
Example 1
char *arr [25];
1. The name of the variable being declared is “arr”.
2. Moving right from here (i.e., spiraling out in anti-clockwise direction), we get [25]; hence, we
say “array of 25”.
3. Spiraling forward anti-clockwise, we find *. Hence, “pointer(s) to”.
4. Finally, between ‘]’ and ‘;’, we get ‘char’, i.e. “character”.
Putting the pieces together, we get the following:
“arr is an array of 25 pointers to character”, OR “arr is an array containing 25 elements, each
element being a pointer to a character”.
Example 2
Moving on to the next example…
long (* ptr_fn) (void);
1. The variable is “ptr_fn”.
2. Spiraling inside the parenthesis, “pointer to”.
3. Next is … “function taking no parameters and returning”.
4. Lastly, “long”.
Hence, “ptr_fn is a pointer to a function taking no parameters and returning a long”.
Example 3
Let’s go to the next example…
5. 4
int (*(* cryptic) (long)) [10];
1. “cryptic” is the variable being declared here.
2. It is a “pointer to”
3. “function taking a long and returning”
4. “pointer to”
5. “an array of 10”
6. “int”.
Hence, “cryptic is a pointer to a function taking a long and returning a pointer to an array of 10
integers”.
Example 4
The last example….
float *(*(* r_u_mad) (int)) (double);
1. “r_u_mad”
2. “pointer to”
3. “function taking an int and returning”
4. “pointer to”
5. “function taking a double and returning”
6. “pointer to”
7. “float”
Hence, “r_u_mad is a pointer to a function taking an integer and returning a pointer to a function
taking a double and returning a pointer to a float”.
Finally, we are thorough!! But things do not have to be this difficult! C is powerful enough to make
these declarations simple. We just need to remember the thumb-rules, which we formed to
circumvent the eccentricity of some whiz kid and apply a few more to use typedef for the same
purpose. Although these examples still inspire the same awe and reverence as it was doing when we
were at on page 1 in ‘Introduction’ section, let us move forward and simply them.
6. 5
On the way to Simplification
typedef is C’s boon to us when we are caught in these kinds of circumstances. To avoid writing those
complex and circuitous declarations, we can leverage this keyword. I am going to use the same
examples as above to show how we can use typedef effectively. Steps to simplification are easy.
Apply the ‘divide & conquer’ rule to break the declaration into smaller steps and give them to the
compiler for creating easy-to-grasp types.
An interesting way to understand typedef may be to ignore it! Read it as if a variable is being
declared - just remember that once the variable is completely read, the complier declares a type
rather than a variable. It becomes a completely new compiler type and as valid as any basic data
type.
7. 6
Examples Revisited
Let us go to the examples:
Simplify Example 1
char *arr [25];
“arr is an array of 25 pointers to character”
Starting from the end, let us create a new type, “pointer to character”. If we write,
char * pt_ch;
we declare a variable ‘pt_ch’ of type “pointer to character”. Putting a typedef before it, we get the
new type “pointer to character”.
typedef char * pt_ch;
Before proceeding, let us take a quick look at another benefit that we can derive from this - relieving
us from the dilemma of where to put ‘*’ while declaring a pointer.
Both the following declarations will declare a pointer to a character.
char* ch1;
char *ch2;
But if we need to declare them together, will the following be enough?
char* ch1, ch2;
Answer is a ‘no’. ‘ch1’ is a pointer to a character, but ‘ch2’ is just a character. But if we use the new
type, which we have just created, the confusion will not arise.
pt_ch ch1, ch2;
The above statement declares both ‘ch1’ and ‘ch2’ as pointers to character.
Coming back to the original problem, creating an “array of 25 pointers to character” is now just like
declaring a variable of any basic type.
pt_ch arr[25];
Let us move on to the next example.
8. 7
Simplify Example 2
long (* ptr_fn) (void);
“ptr_fn is a pointer to a function taking no parameters and returning a long”.
Attacking the last portion first, we create a new type, “function taking no parameters and returning a
long” as follows:
typedef long fn_type (void);
The function prototype is preceded by a typedef to create the new type. Now we can declare ptr_fn
as easily as below.
fn_type *ptr_fn;
/* One more level of typedef is also possible, but do we really need that? */
Let us have a look at the 3rd example.
Simplify Example 3
int (*(* cryptic) (long)) [10];
“cryptic is a pointer to a function taking a long and returning a pointer to an array
of 10 integers”.
As usual we begin with the last part, which is “pointer to an array of 10 integers”. Dividing it in even
smaller steps, we generate the types as following:
typedef int arr_of_10_int[10];
typedef arr_of_10_int *p_arr_10_int;
Now, for a function taking a long as parameter and returning p_arr_10_int, we have,
typedef p_arr_10_int fn_t(long);
Now, just create ‘cryptic’ as following:
fn_t *cryptic;
9. 8
The last example
Simplify Example 4
float *(*(* r_u_mad) (int)) (double);
“r_u_mad is a pointer to a function taking an integer and returning a pointer to a
function taking a double and returning a pointer to a float”.
Let us approach this awesome example also in the same way as we did before.
We start with the last part, i.e., “pointer to float”.
typedef float *ptf;
Now we find “function taking a double and returning ptf”. Hence, we write,
typedef ptf fn_dble(double);
Moving forward, we get “pointer to fn_dble”. Therefore,
typedef fn_dble *p_fn_dble;
Finally, “function taking an integer and returning p_fn_dble” -
typedef p_fn_dble maddening(int);
Hence, the declaration now becomes
maddening r_u_mad;
This brings us to an end of my venture of putting down some tips on how to simplify complex
declarations in C.