How do I understand following complicated declarations?
char (*(*f())[])();
char (*(*X[3])())[5];
void (*f)(int,void (*)());
char far *far *ptr;
typedef void (*pfun)(int,float);
int **(*f)(int**,int**(*)(int **,int **));
How do I understand following complicated declarations?
char (*(*f())[])();
char (*(*X[3])())[5];
void (*f)(int,void (*)());
char far *far *ptr;
typedef void (*pfun)(int,float);
int **(*f)(int**,int**(*)(int **,int **));
As others have pointed out, cdecl is the right tool for the job.
If you want to understand that kind of declaration without help from cdecl, try reading from the inside out and right to left
Taking one random example from your list char (*(*X[3])())[5];
Start at X, which is the identifier being declared/defined (and the innermost identifier):
char (*(*X[3])())[5];
^
X is
X[3]
^^^
X is an array of 3
(*X[3])
^ /* the parenthesis group the sub-expression */
X is an array of 3 pointers to
(*X[3])()
^^
X is an array of 3 pointers to function accepting an unspecified (but fixed) number of arguments
(*(*X[3])())
^ /* more grouping parenthesis */
X is an array of 3 pointers to function accepting an unspecified (but fixed) number of arguments and returning a pointer
(*(*X[3])())[5]
^^^
X is an array of 3 pointers to function accepting an unspecified (but fixed) number of arguments and returning a pointer to an array of 5
char (*(*X[3])())[5];
^^^^ ^
X is an array of 3 pointers to function accepting an unspecified (but fixed) number of arguments and returning a pointer to an array of 5 char.
Read it out from the inside, similar to how you would solve equations such as {3+5*[2+3*(x+6*2)]}=0
- you'd start by solving what's inside ()
then []
and finally {}
:
char (*(*x())[])()
^
This means that x
is something.
char (*(*x())[])()
^^
x
is a function.
char (*(*x())[])()
^
x
returns a pointer to something.
char (*(*x())[])()
^ ^^^
x
returns a pointer to an array.
char (*(*x())[])()
^
x
returns a pointer to an array of pointers.
char (*(*x())[])()
^ ^^^
x
returns a pointer to an array of pointers to functions
char (*(*x())[])()
^^^^
Meaning the array pointer returned by x
points to an array of function pointers that point to functions that return a char.
But yeah, use cdecl. I used it myself to check my answer :).
If this is still confusing you (and it probably should), try to do the same thing on a piece of paper or in your favorite text editor. There's no way of knowing what it means just by looking at it.
Sounds like a job for the cdecl tool:
cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char
I looked around for an official homepage for the tool, but couldn't find one that seemed genuine. In Linux, you can typically expect your distribution of choice to include the tool, so I just installed it in order to generate the above sample.
You should be using cdecl tool. It should be available on most Linux distributions.
e.g. for this function, it will return you:
char (*(*f())[])();
- declare f as function returning pointer to array of pointer to function returning char
void (*f)(int,void (*)());
- prototype of function pointer f. f is a function that takes two parameters, the first one is int, and the second one is a function pointer for a function which returns void.
char far *far *ptr;
- ptr is a far pointer to a far pointer (which points to some char/byte).
char (*(*X[3])())[5];
- X is an array of 3 pointers to function accepting an undeterminate number of arguments and returning a pointer to an array of 5 char.
typedef void (*pfun)(int,float);
- declaring function pointer pfun. pfun is a fuctnion that takes two parameters, first one is int, second one is of float type. the function does not have a return value;
e.g.
void f1(int a, float b)
{ //do something with these numbers
};
Btw, complicated declarations as the last one are not seen often. Here is an example I just made up for this purpose.
int **(*f)(int**,int**(*)(int **,int **));
typedef int**(*fptr)(int **,int **);
int** f0(int **a0, int **a1)
{
printf("Complicated declarations and meaningless example!\n");
return a0;
}
int ** f1(int ** a2, fptr afptr)
{
return afptr(a2, 0);
}
int main()
{
int a3 = 5;
int * pa3 = &a3;
f = f1;
f(&pa3, f0);
return 0;
}
It appears that your actual question is this:
What's the use case for a pointer to a pointer?
A pointer to a pointer tends to show up when you have an array of some type T, and T itself is a pointer to something else. For example,
char *
.char *x[10]
: x
is an array of 10 pointers to char
, aka 10 strings.At this point, you might be wondering where char **
comes in. It enters the picture from the very close relationship between pointers arithmetic and arrays in C. An array name, x
is (almost) always converted to a pointer to it's first element.
char *
. char **
.In C, array E1[E2]
is defined to be equivalent to *(E1 + E2)
. Usually, E1
is the array name, let's say x
, which automatically converted to a char **
, and E2
is some index, say 3. (This rule also explains why 3[x]
and x[3]
are the same thing.)
Pointers to pointers also show up when you want a dynamically allocated array of some type T
, which is itself a pointer. To start with, let's pretend we don't know what type T is.
T *vec
.T *
can serve as the base of a contiguous sequence of T
's in memory.n
elements? vec = malloc(n * sizeof(T))
;This story is true for absolutely any type T
, and so it's true for char *
.
vec
if T
is char *
? char **vec
.Pointers to pointers also show up when you have a function that needs to modify an argument of type T, itself a pointer.
strtol
: long strtol(char *s, char **endp, int b)
.strtol
converts a string from base b
to an integer. It wants to tell you how far into the string it got. It could perhaps return a struct containing both a long
and a char *
, but that's not how it's declared.char *
.char **
.If you wander down this path long enough, you can also run into T ***
types, although you can almost always restructure the code to avoid them.
Finally, pointers to pointers appear in certain tricky implementations of linked lists. Consider the standard declaration of a doubly-linked list in C.
struct node {
struct node *next;
struct node *prev;
/* ... */
} *head;
This works fine, although I won't reproduce the insertion/deletion functions here, but it has a little problem. Any node can be removed from the list (or have a new node inserted before it) without reference the head of the list. Well, not quite any node. This isn't true of the first element of the list, where prev
will be null. This can be moderately annoying in some kinds of C code where you work more with the nodes themselves than with the list as a concept. This is a reasonably common occurrence in low-level systems code.
What if we rewrite node
like this:
struct node {
struct node *next;
struct node **prevp;
/* ... */
} *head;
In each node, prevp
points not at the previous node, but at the previous nodes's next
pointer. What about the first node? It's prevp
points at head
. If you draw out a list like this (and you have to draw it out to understand how this works) you'll see that you can remove the first element or insert a new node before the first element without explicitly referencing head
by name.
Remo.D's answer for reading functions is a good suggestion. Here are some answers to the others.
One use-case for a pointer to a pointer is when you wish to pass it to a function that will modify the pointer. For example:
void foo(char **str, int len)
{
*str = malloc(len);
}
Also, this could be an array of strings:
void bar(char **strarray, int num)
{
int i;
for (i = 0; i < num; i++)
printf("%s\n", strarray[i]);
}
Typically, one shouldn't use declarations this complicated, although sometimes you do need types that are pretty complicated for things like function pointers. In those cases, it's much more readable to use typedefs for intermediate types; for example:
typedef void foofun(char**, int);
foofun *foofunptr;
Or, for your first example of "function returning pointer to array[] of pointer to function returning char", you might do:
typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }
In practice, if you're writing anything sane, many of those things will have meaningful names of their own; for instance, these might be functions returning names (of some sort), so fun_returning_char
could be name_generator_type
, and array_of_ptrs_to_fun
could be name_generator_list
. So you could collapse it a couple of lines, and only define those two typedefs -- which are probably going to be useful elsewhere in any case.
malloc
is false, it should be (char *)
. Better, remove it completely, it's not necessary in C (in C++ it would, but in C++ you should rather use new
). As for the typedefs for function pointers, it's a matter of taste. I do not like typedefs of function pointers (or even of any pointers in general), it obfuscates the code. Seeing directly if a variable is a pointer or not is more important than 2 levels of *
. –
Undersigned x: function returning pointer to array[] of pointer to function returning char" - huh?
You have a function
That function returns a pointer.
That pointer points to an array.
That array is an array of function pointers(or pointers to functions)
Those functions returns char*.
what's the use case for a pointer to a pointer?
One is to facilitate return values through arguments.
Lets say you have
int function(int *p)
*p = 123;
return 0; //success !
}
You call it like
int x;
function(&x);
As you can see, for function
to be able to modify our x
we have to pass it a pointer to our x.
What if x
was not an int, but a char *
? Well, its still the same, we have to pass a pointer to that. A pointer to a pointer:
int function(char **p)
*p = "Hello";
return 0; //success !
}
You call it like
char *x;
function(&x);
char far *far *ptr;
This is an obsolete Microsoft form, dating back to MS-DOS and very early Windows days. The SHORT version is that this is a far pointer to a far pointer to a char, where a far pointer can point anywhere in memory, as opposed to a near pointer which could only point anywhere in 64K data segment. You really don't want to know the details about Microsoft memory models for working around the utterly brain-dead Intel 80x86 segmented memory architecture.
typedef void (*pfun)(int,float);
This declares pfun as a typedef for a pointer to a procedure that takes an int and a float. You would normally use this in a function declaration or a prototype, viz.
float foo_meister(pfun rabbitfun)
{
rabbitfun(69, 2.47);
}
We have to evaluate all pointer declaration statements from left to right, starting from where the pointer name or declaration name is declared on the statement.
While evaluating the declaration, we have to start from the innermost parenthesis.
Start with the pointer name or function name followed by the rightmost characters in the parenthersis and then follwed by the leftmost characters.
Example:
char (*(*f())[])();
^
char (*(*f())[])();
^^^
In here f is a function name, so we have to start from that.
f()
char (*(*f())[])();
^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:
char (*(*f())[])();
^
f() *
We have completed the inner parenthesis characters, and now we have to go back to one level behind this:
char (*(*f())[])();
------
Now take [], because this is on the right side of the current parenthesis.
char (*(*f())[])();
^^
f() * []
Now take the * because there is no character on the right side.
char (*(*f())[])();
^
char (*(*f())[])();
^
f() * [] *
char (*(*f())[])();
Next evaluate the outer open and close parenthesis, it's indicating a function.
f() * [] * ()
char (*(*f())[])();
Now we can add data type at the end of the statement.
f() * [] * () char.
char (*(*f())[])();
Final answer:
f() * [] * () char.
f is a function returning pointer to array[] of pointers to function returning char.
Assume, for argument's sake (pun, pun) that the argument lists in "char (*(*f())[])();" are empty. Technically, it's still K&R (the last I checked), but this may phase out by the time the next ISO standard comes about for C ... (check, check) ... oops, any time now. And I just noticed: they're actually putting in "typeof", I see, as I've long suggested.
The declaration is a statement made about the declared item, as a template. This statement, itself, is a declaration and entails more statements, and so on. In the following, it will use ":" for "is", the prefix "p T" for "pointer to T", "a T" for "array of T", "an T" for "array of n T's", and "(...) → T" for "is a function taking arguments of types "(...)" and returning T, where T denotes a type and n denotes a positive integer.
The resulting sequence, for the first example, is:
char (*(*f())[])()
(*(*f())[])() : char
*(*f())[] : () → char
(*f())[] : p (() → char)
*f() : a p (() → char)
f() : p a p (() → char)
f: () → p a p (() → char))
Similarly, for the second example:
char (*(*X[3])())[5]
(*(*X[3])())[5] : char
*(*X[3])() : a5 char
(*X[3])() : p a5 char
*X[3] : () → p a5 char
X[3] : p (() → p a5 char)
X : a3 p (() → p a5 char)
For the third example, you'll need a parameter type for "void ()()", which is understood equivalently as a declaration "void (_)()" for an unnamed parameter, which we'll denote "_". This needs to be worked out first:
void (*_)()
(*_)() : void
*_ : () → void
_ : p (() → void)
Correspondingly, we have:
void (*f)(int,void (*)())
(*f)(int,void (*)()) : void
*f : (int, p (() → void)) → void
f : p ((int, p (() → void)) → void)
The next example uses attributes, but works similarly:
char far *far *ptr
far *far *ptr : char
*far *ptr : far char
far *ptr : p far char
*ptr : far p far char
ptr : p far p far char
The "far" attribute actually goes with "p", which should then be denoted p_far, with "p_tar T" denoting a "far pointer to T", so that this would be written as:
ptr : p_far p_far char
The "typedef" statements don't declare but equate. So ":" gets replaced by "=", with everything else working the same as before:
typedef void (*pfun)(int,float)
(*pfun)(int,float) = void
*pfun = (int, float) → void
pfun = p ((int, float) → void)
Finally, for the last example we need parameter types for "int**" and "int**(*)(int **,int **)". These are worked out as follows:
int**_
**_ : int
*_ : p int
_ : p p int
and
int**(*_)(int **,int **)
**(*_)(int **,int **) : int
*(*_)(int **,int **) : p int
(*_)(int **,int **) : p p int
*_ : (p p int, p p int) → p p int
_ : p ((p p int, p p int) → p p int)
Thus,
int **(*f)(int**,int**(*)(int **,int **))
**(*f)(int**,int**(*)(int **,int **)) : int
*(*f)(int**,int**(*)(int **,int **)) : p int
(*f)(int**,int**(*)(int **,int **)) : p p int
*f : (p p int, p ((p p int, p p int) → p p int)) → p p int
f : p ((p p int, p ((p p int, p p int) → p p int)) → p p int)
If you declare "typedef int **ppint;" then you could write the last result as
f : p ((ppint, p ((ppint, ppint) → ppint)) → ppint)
In my C-BC Interpreter, (at the time of writing) the "declaration unwinding" process takes place in the source file "Statement.c", under the routine "Declarator()", which assigns 0 to all scalar types, equates aT to 2T + 1 and pT to 2T + 2. Neither C-BC, nor the language (BC) it is based on, have arrays of or functions to pointers, so this design will work in that setting, but would be broken if trying to scale up to a more type-rich language, like C. I might graft a similar "declaration unwinding" process in my fork PicoC of picoc once I fix up its coding ... and I might also graft in C-BC's execution machine or one similar to it.
A collated syntax for C 1989/1990, C 1999 and C 2010/2011 may be found (at present) in the C Syntax pdf added into the PicoC repository. I haven't decided how much of the syntax to incorporate, but the declaration syntax is listed there.
All declarations have the same general form "D → Sp Dc", with a few minor variations depending on context; consisting of the declaration "D", the specifier "Sp" (the base for the declaration, which could be a scalar type or structure, union or enumerator list) and the declarator "Dc" (the name of the object and the indexing by "function of", "pointer to", "array of" and attributes). As noted there, and seen in the examples above, such as the third example, parameters need not list an actual name in the declarator, and type specifications can't list any names.
Forget about 1 and 2 - this is just theoretical.
3: This is used in the program entry function int main(int argc, char** argv)
. You can access a list of strings by using a char**
. argv[0] = first string, argv[1] = second string, ...
Passing a pointer as an argument to a function lets that function change the contents of the variable pointed to, which can be useful for returning information by means other than the function return value. For example, the return value might already be used to indicate error/success, or you might want to return multiple values. The syntax for this in the calling code is foo(&var), which takes the address of var, i.e., a pointer to var.
So as such, if the variable whose contents you want the function to change is itself a pointer (e.g., a string), the parameter would be declared as a pointer to a pointer.
#include <stdio.h>
char *some_defined_string = "Hello, " ;
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated
int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
*str1 = some_defined_string ;
*str2 = alloc_string() ;
*str3 = "!!" ;
if (str2 != 0) {
return 0 ; //successful
} else {
return -1 ; //error
}
}
main()
{
char *s1 ; //uninitialized
char *s2 ;
char *s3 ;
int success = point_me_to_the_strings(&s1, &s2, &s3) ;
printf("%s%s%s", s1, s2, s3) ;
}
Note that main() does not allocate any storage for the strings, so point_me_to_the_strings() does not write to str1, str2, and str3 as it would if they were passed as pointers to chars. Rather, point_me_to_the_strings() changes the pointers themselves, making them point to different places, and it can do this because it has pointers to them.
I thought I'd add the way I think about declarations since almost all the explanations I've found on the web never really cleared the confusion for me.
The first thing to do is to separate the specifier sequence from the declarator.
The specifier sequence only ever contains keywords like const
, volatile
, inline
, etc.. and a type specifier such as int
, char
, unsigned long
, etc.. It will never contain parenthesis (
)
, brackets [
]
, asterisks *
, or the name being declared.
The declarator OTOH always contains the (user provided) name of the entity being declared, optionally surrounded by other symbols such as parenthesis (
)
, brackets [
]
, or asterisks *
s. I'm using the word entity to refer to a generalized variable, array, pointer, or function.
It's important to remember that the type specification keywords in the specifier sequence do not refer to the thing being named in the declaration (the declared entity). They refer to the specified entity that is the result of the declarator.
Below is an example.
const char *names[10];
Broken up...
const char *names[10] ;
^^^^^^^^^^ ^^^^^^^^^^
|-spec-seq-||-declrtor-|
const char
does not refer to the declared entity names
. It refers to the specified entity that is the result of the declarator *names[10]
.
To obtain the declared entity type from the specified entity type, we start from the declared name and work backwards. According to the order of operations, adjacent brackets are read before asterisks. names[10]
says we start with the declared entity names
and pick some element at an offset between 0 and 9. *names[10]
says we then dereference the chosen element.
Because we, first, pick an element at an offset between 0 and 9, and second, dereference the thing we just picked, we have determined that names
must be an array of length 10 containing pointers to the specified entity type.
The type specification says the specified entity type is const char
.
Putting this information together, the declaration should be read as...
Declare
names
as an array (length 10) of pointers to typeconst char
.
No suppose instead we have...
const char (*names)[10];
The order of operations says we do what's in the parenthesis first. So (*names)[10]
says, first, dereference names
, second, pick an element at an offset between 0 and 9.
Therefore names
is now a pointer to an array of specified entities. The specified entity type is still const char
, so we read the declaration as...
Declare
names
as a pointer to an array (length 10) of elements of typeconst char
.
Now suppose we have...
const char *const names[10];
The keyword const
can appear inside a declarator when it appears immediately after an asterisk. The meaning is that the memory address being dereferenced is constant. This is the way to declare a constant pointer. The declaration thus reads...
Declare
names
as an array (size 10) ofconst
pointers toconst char
.
This variable will now need to be initialized with a list of 10 string literals.
Now we consider a function declaration.
int foo(const char **);
Function declarations are different from variable declarations in that each argument in the argument list is another declaration, though it's an anonymous declaration, which means it doesn't have a name.
Lets first figure out the argument.
const char **
Because the declaration is anonymous, to better understand we need to mentally insert something where the name would normally be.
const char **_anon_
If we dereference _anon_
twice, we get the specified entity. Therefore the argument is a pointer to a pointer to a specified entity of type const char
. This could be an array of names.
Now consider the full declarator...
foo(const char **);
The parenthesis to the right of foo
means "pass the following parameters to foo
", which implies foo
is a function. The specified entity is then the thing returned by foo
.
Therefore...
int foo(const char **);
can be read as...
Declare
foo
as a function taking an argument of type [ pointer to pointer toconst char
] and returning typeint
.
Now consider...
int (*foo)(const char **);
The declarator (*foo)(const char **)
says first, dereference foo
, second, pass an argument of type - pointer to pointer to const char
- to the result.
Because foo
must be dereferenced before an argument can be passed to it, foo
is now a pointer to a function. The declaration is thus...
Declare
foo
as a pointer to a function, taking an argument of type [ pointer to pointer toconst char
] and returning typeint
.
Now consider the case without the parenthesis...
int *foo(const char **);
Due to the order of operations being different, the meaning is completely changed. We now, first, pass an argument of type - pointer to pointer to const char
- to foo
, then second, dereference the return value to obtain an int
.
This means foo
is not a pointer to a function, but a function returning a pointer to an int
. The full declaration is thus...
Declare
foo
as a function, taking an argument of type [ pointer to pointer toconst char
] and returning type [ pointer to int ].
Now what about this...
int (*foo(const char **))();
As before, the argument passed to foo
is "pointer to pointer to const char
". However, notice that now when we dereference the entity returned by foo
we don't immediately get the specified int
. The trailing pair of parenthesis indicates that we need to treat the dereferenced entity as a function that must be called to obtain the specified entity of type int
. This means foo
is no longer returning a pointer to int
, but a pointer to another function that returns an int
.
In summary, int (*foo(const char **))(double)
is a declaration of a function that returns a function pointer. The declaration is read as...
Declare
foo
as a function taking an argument of type [ pointer to pointer toconst char
] and returning type [ pointer to function returning type int ].
Now the most horrible abomination of a declaration I could think up. You will never see anything like this IRL, but if you can read it for practice you should be able to read almost anything.
const char *const (*foo(int *[4], int (*)(const char *)))(double *(*)[]);
To stay sane, we need to break this into parts and start from the inside.
First look at this underlined phrase on its own.
const char *const (*foo(int *[4], int (*)(const char *)))(double *(*)[]);
^^^^^^^^
The anonymous name needs to go between the *
and the [
, so the relevant sub-declaration is int *_anon_[4]
. This says if we select an element at an offset between 0 and 3 from _anon_
, then dereference the result, we get an int
. Thus we read the underlined phrase as.
array (size 4) of pointers to type
int
.
Now consider this underlined phrase.
const char *const (*foo(int *[4], int (*)(const char *)))(double *(*)[]);
^^^^^^^^^^^^^^^^^^^^^
Here there is an anonymous name immediately after the *
in (*)
and also immediately after the *
in (const char*)
. Lets read the inner phrase first.
const char *_anon_
Dereferencing _anon_
gives a const char
, so the phrase const char *
is just "pointer to const char
". Now on to the full underlined phrase.
int (*_anon_)(const char *)
If we dereference _anon_
, and then pass "pointer to const char
" to it as an argument, we get an int
. Therefore the phrase int (*)(const char*)
reads...
pointer to function taking argument [ pointer to
const char
] and returningint
Now one more inner phrase.
const char *const (*foo(int *[4], int (*)(const char *)))(double *(*)[]);
^^^^^^^^^^^^^
This is equivalent to double *(*_anon_)[]
. We dereference _anon_
, then select an element at some specified offset, then, finally, dereference that element. As a result we get a specified entity of type double
. The phrase double *(*)[]
thus reads...
pointer to array (unknown size) of pointers to type
double
Now consider the full declaration again.
const char *const (*foo(int *[4], int (*)(const char *)))(double *(*)[]);
To simplify, replace the phrases we've already decoded with labels.
const char *const (*foo([...1...],[...2...]))([...3...]);
If we pass arguments [...1...]
and [...2...]
to foo
, then dereference the return value, then pass an argument of type [...3...] to that, then, finally, dereference the constant address returned as a result, we get a specified entity of type const char
.
This means the full declaration is...
Declare
foo
as a function taking arguments of type [...1...], and [...2...], and returning a pointer to [ function taking argument of type [...3...] and returning [const
pointer to typeconst char
] ].
or...
Declare
foo
as a function taking arguments of type [ array (size 4) of pointers to typeint
], and [ pointer to function taking argument [ pointer toconst char
] and returningint
], and returning pointer to [ function taking argument of type [ pointer to array (unknown size) of pointers to typedouble
] and returning [const
pointer to typeconst char
] ].
So foo
here is a function that takes an array of pointers to int
and a function pointer as it's arguments and returns another function pointer. The need to specify the arguments and return values of both function pointers makes the declaration incredibly convoluted.
© 2022 - 2025 — McMap. All rights reserved.