Monday, October 15, 2007

A brief discussion of C declarators

So while I'm waiting for a new virtual image to finish extracting so that I can recover from the disaster that was Friday afternoon, I thought I'd write up a brief introduction to C declarators.

Declarations in C can be confusing to someone not familiar with the language. Declarations like

int x;
float y;

are fairly straightforward (x is an integer, y is a single-precision real number), but then you come across something like this:

int *(*(*foo)[10])(int (*bar)[20]);

What does that even mean? How do you read declarations like that?

C follows the paradigm of declaration mimics use: in other words, the declaration of an object must look as much like how it will be used in the code that follows. This is probably best explained with a few examples.

Suppose we have a pointer to an integer, and we want to refer to the pointed-to integer value. To do this, we would use the dereference operator, '*':

*x = 5;
printf("%d\n", *x);


The type of the expression *x is int, so our declaration looks like this:

int *x;

The keyword int is the type specifier. It provides the basic type information for item being declared. The expression *x is called the declarator. It introduces the name of the thing being declared, as well as additional type information. In the pointer example above, x is the name of the variable, and its type is "pointer to int". However, the "pointerness" of x is given by the presence of the * operator in the declarator.

Let's look at a more complicated example. Suppose we have an array of pointers to int, and we want to refer to the integer value pointed to by element i of the array. We would write

x = *arr[i];

The declaration for the array arr would look like the following:

int *arr[N];

where N is a constant integer expression for the size of the array (I'm not familiar enough with C99's variable-sized arrays to provide a usable example). The type of arr is "N-element array of pointers to int." Again, the "int-ness" of arr is given by the type specifier int, but the "array-ness" and "pointer-ness" are given by the declarator *arr[N].

Note that the * and [] operators are bound to the identifier, not the type specifier. Whitespace makes no difference; in other words, the statements

int* x;

and

int *x;

are identical. This also means that the statement

int* x, y;

only declares x as a pointer; y is declared as a regular integer.

So what about the declaration

int *(*(*foo)[10])(int (*bar)[20]);

How do you read something like that? The trick is to find the leftmost identifier and work your way out, remembering that () and [] bind before * (IOW, *a[] is an array of pointer, (*a)[] is a pointer to an array, *f() is a function returning a pointer, and (*f)() is a pointer to a function). So in this case,

foo -- foo
*foo -- is a pointer
(*foo)[10] -- to a 10-element array
*(*foo)[10] -- of pointers
(*(*foo)[10])() -- to functions
(*(*foo)[10])(int (*bar)[20]) -- taking a pointer to a 20-element array of int
*(*(*foo)[10])(int (*bar)[20]) -- returning pointer
int *(*(*foo)[10])(int (*bar)[20]) -- to int

Labels: ,

0 Comments:

Post a Comment

<< Home