Even 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))
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 https://www.cdecl.org/ (thanks @FrankSansC)
- Navigating Memory in C++: A Guide to Using std::uintptr_t for Address Handling - February 22, 2024
- Embedded Expertise: Beyond Fixed-Size Integers; Exploring Fast and Least Types - January 15, 2024
- Disassembling a Cortex-M raw binary file with Ghidra - December 20, 2022
Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.
Useful stuff. Thank you!
However, the theme is a bit popular in related books, etc.
PS
Seems to be a little slip: "where ‘x’ is an (identifier for an) integer" should be "where ‘int’ is an (identifier for an) integer" or "'x' declared as of integer (data) type".
PPS
Suppose this will be followed by discussing of 'volatile' and 'const' usage.
Sorry to (maybe) disappoint you, but your assumption is wrong about functions that never return an array. An example:
struct ret {
int a[10];
} Function (void);
Pavel, I don't understand your two alternatives to Niall's perfectly lucid "where 'x' is an (identifier for an) integer". Your first alternative is just wrong - 'int' is not an identifier but a reserved word, in C, indicating a type. Your second alternative approximates to what Niall wrote in the first place, but does so less clearly. I feel something has been lost, here, in your translation from English to Russian and back again 🙂
Pjotr, your Function does not return an array; it returns a structure containing an array.
A prerequisite for successful pedantry is meticulous accuracy.
Pingback: Unscrambling C Declarations | Roman's knowledgebase
Here is one I still gotta re-re-read when I do not use it for a while.
#define SBIT(port,pin) ((*(volatile struct bits*)&port).b##pin)
Its used to access register bits (via a structure) like a variable so you could say:
#define LED0 SBIT(PORTB,4)
LED0 = 1;
LED0 = 0;
Your article very clearly explains C pointers. Even Better than Deep C Secrets by Peter Linden chapter 3.