How do I understand complicated function declarations?
Asked Answered
O

13

36

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 **));
Olshausen answered 19/9, 2009 at 16:8 Comment(18)
Is homework against the site rules?Gingrich
You won't learn anything by letting others explain a few declarations. Similar ones have been explained already. You have to learn the mechanism to read them, instead, which is already explained on the web. So, best you read the C Standard, which contains the grammar of them. After which you can parse them yourself.Titbit
This is certainly a real and valid question so I won't vote to close, but for anyone that posts homework on the site, please tag it as such and make a best-effort to solve the problem first and outline where you are stuck. Most people will be glad to help then.Couplet
Also don't forget the geordi C++ bot: xs4all.nl/~weegen/eelis/geordi . Also helps understanding C declarations, of course.Titbit
@Ravi, you haven't graduated yet, so you aren't at "graduation level". You are at undergraduate level. It looks like homework to me. "I read these declaration somewhere." Right ...Fathomless
So, then, where did you get these declarations from?Kelsi
Pardon me if I start to sound annoying, but what's the name of the book? Surely it tells you how to parse these declarations near-by?Kelsi
@Ravi, you mean the geordi thing? Come to #geordi on freenode. There its creator (eelis) is sitting too and you can enjoy the fun abusing it :)Titbit
Unrelated to this post, but in India, "graduation level" is what Americans call "undergraduate." :-)Tokenism
@Ravi, then i'm sorry, i don't quite understand what you meant by "I am impressed by your comment come suggestion".Titbit
Be happy now.I've tagged it "homework"Olshausen
cdecl.orgHemingway
It's mainly academic fun. One would normally use typedefs to make such declarations more comprehensible.Buyer
1 and 2 hurt my brain. Code like this should never be written!Gimmick
Duplicate of #1449349Wingate
See also: unixwiz.net/techtips/reading-cdecl.html, ericgiguere.com/articles/reading-c-declarations.html, cm.bell-labs.com/cm/cs/who/dmr/chist.htmlWingate
See also: unixwiz.net/techtips/reading-cdecl.html, ericgiguere.com/articles/reading-c-declarations.html, cm.bell-labs.com/cm/cs/who/dmr/chist.htmlWingate
Thanks everyone. Good stuff. Lots of useful links, I've started plowing through them.Micky
S
78

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.

Saulsauls answered 19/9, 2009 at 16:28 Comment(0)
L
16

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.

Loughlin answered 18/4, 2010 at 18:27 Comment(0)
A
13

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.

Andreandrea answered 19/9, 2009 at 16:11 Comment(0)
O
5

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;
}
Oleaginous answered 19/9, 2009 at 16:21 Comment(2)
Sorry.I'm not a linux user at all.Is it available for windows?Olshausen
I have found MS-DOS version that you can run on Windows (at least it can run on my Windows XP machine) - here it is: simtel.net/product.php?url_fb_product_page=41564Oleaginous
D
5

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,

  • What's a string in C? Typically, it's a char *.
  • Would you like an array of strings from time to time? Sure.
  • How would you declare one? 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.

  • What's the first element? A char *.
  • What's a pointer to the first element? A 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.

  • If we want a dynamically allocated vector of T's, what type do we need? T *vec.
  • Why? Because we can perform pointer arithmetic in C, any T * can serve as the base of a contiguous sequence of T's in memory.
  • How do we allocate this vector, say of n elements? vec = malloc(n * sizeof(T));

This story is true for absolutely any type T, and so it's true for char *.

  • What's the type of 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.

  • Look at the declaration for strtol: long strtol(char *s, char **endp, int b).
  • What's this all about? 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.
  • Instead, it returns its second result by passing in the address of a string which it modifies before returning.
  • What's a string again? Oh yeah, char *.
  • So what's an address of a string? 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.

Denicedenie answered 19/4, 2010 at 1:32 Comment(1)
I use the linked list example because I have an object containing a list of structs of a different type. By having prevp point to a pointer it doesn't mater if it's pointing to a "next" pointer in a similar struct, or the pointer in the object that contains the list.Crowder
G
2

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.

Gouveia answered 18/4, 2010 at 18:19 Comment(2)
Your typecast on the first 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
Thanks; editing the malloc. And I agree that the typedefs may be a little excessive (as, to some extent, I was exaggerating to make a point) -- my preferred style is that you typedef the function type, and then explicitly make a pointer to it in the declarations.Gouveia
J
2

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); 
Jibe answered 18/4, 2010 at 18:27 Comment(0)
M
1
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);
}
Mapp answered 19/9, 2009 at 16:24 Comment(0)
G
1

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.

Grisham answered 24/4, 2015 at 5:6 Comment(0)
V
1

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.

Velocity answered 4/3, 2024 at 1:52 Comment(0)
G
0

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, ...

Gimmick answered 18/4, 2010 at 18:14 Comment(0)
D
0

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.

Duster answered 19/4, 2010 at 0:37 Comment(0)
E
0

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 type const 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 type const 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) of const pointers to const 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 to const char ] and returning type int.

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 to const char ] and returning type int.

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 to const 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 to const 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 returning int

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 type const char ] ].

or...

Declare foo as a function taking arguments of type [ array (size 4) of pointers to type int ], and [ pointer to function taking argument [ pointer to const char ] and returning int ], and returning pointer to [ function taking argument of type [ pointer to array (unknown size) of pointers to type double ] and returning [ const pointer to type const 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.

Elishaelision answered 19/5, 2023 at 8:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.