Unscrambling C Declarations
December 9th, 2009Even though the C programming language has been around since the late 1960’s, many programmers still have trouble understanding how C declarations are formed. This is not unsurprising due to the complexity that can arise when mixing pointer, array and function-pointer declarations.
Very simple ones (specifically those not involving “[]” or “()“) can be read from right-to-left, e.g.
int x
int a[10]
- A function cannot return a function – () foo()
- A function cannot return an array – [] foo ()
- An array cannot hold functions – foo[]()
int x x is an integer
Rule 1: Read from left to right looking for an identifier.
int a[10] x is an array of (ten) integers
void x(int y) x is a function that takes an integer parameter (y) and returns nothing (void)
Rule 2: look right from the identifier for postfix operators () or []. If [] then it is an array, else if () then it is a function.
int * x x is a pointer to an integer
Rule 3: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
const int x x is an integer constant
Rule 4: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
int const x x is a constant integer (This is identical to the previous declaration. This is part of the confusing syntax of the C programming language, but Rule 4 still applies).
int const * x x is a pointer to a constant integer (as above – still confused?)
int * x[10] x is an array of pointers to integers ( Rule 2, Rule 3)
int * x(void) x is a function that returns a pointer to an integer (Rule 2, Rule 3)
int **x x is a pointer to a pointer to an integer (Rule 3, Rule 3)
int * const x
Rule 4b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
int const * const x x is a constant pointer to a constant integer
int (*x)(void) x is a pointer to a function that returns an integer
Rule 2: If the identifier is within parentheses, then evaluate inside the parentheses first
void (*x)(int y) x is a pointer to a function that takes an integer (y) as a parameter and returns void
- Rule 1: Read from left to right looking for an identifier
- Rule 2: If the identifier is within parentheses, then evaluate inside the parentheses first
- Rule 3: look right for postfix operators ( ) or [ ]. If [] then it is an array, else if () then it is a function.
- Rule 4: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
- Rule 5a: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
- Rule 5b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
void (*fpa[10])(int)
Rule 1: From left to right find identifier, this gives us fpa
Rule 2: (*fpa[]) parentheses win, so evaluate inside the parentheses
Rule 3: fpa[10] postfix [] wins; fpa is a ten element array ($ now represents fpa[10])
Rule 4: *$ prefix * wins; fpa is an array of pointers. Now we’ve evaluated inside the parentheses we step outside.
Rule 3: $() postfix, () wins fpa is an array of pointers to functions
Rule 2: void $(int) parentheses; fpa is an array of pointers to functions each taking an integer parameter and returning void
First, as always is rule 1; signal is the identifier. signal is in parentheses, so based on Rule 2 we must evaluate that first. If we match parenthesis then we get:
(*signal(int sig, void(*func)(int)))
(*signal())
void (* signal() )(int)
void (*$)(int)
signal(int sig, void(*func)(int))
style='font-family: "Courier New",Courier,monospace;'>int sig – sig is an integer
void(*func)(int) - func is a pointer to a function that has an integer parameter and returns void.
- signal is a function
- that returns a pointer to a function that has an integer parameter and returns void
- and takes two parameters of
- an integer, and
- a pointer to a function that has an integer parameter and returns void
Avoid by design, as far as possible. If this fails, divide and conquer remembering that typedef is your friend. A typedef declaration does not introduce a new type, only a synonym for the type specified. For example:
MILES m; /* m is of type int */
int_ptr ip; /* ip is of type integer pointer int* */
typedef void (*FuncPtr)(int);
void (*signal(int sig, void(*func)(int)))(int)
FuncPtr signal(int sig, FuncPtr)
void (*fpa[10])(int)
FuncPtr fpa[10]
Rule 1: Read from left to right looking for an identifier
Rule 2: If the identifier is with parentheses, then evaluate inside the parentheses first
Rule 3: look right for postfix operators ( ) or [ ]. If [] then it is an array, else if () then it is a function.
Rule 4: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
Rule 5a: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
Rule 5b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
Also check out http://www.cdecl.org/ (thanks @FrankSansC)