Today I was thinking about C declaration syntax, and I remembered that it can look fairly silly sometimes!
read more / close
C declaration syntax nonsense
Everyone knows how a declaration looks in C! You write the type, and then the name of the thing being declared.
int my_int;
Right? Well, it’s a bit more complicated than that. The actual syntax is to write something that resembles an expression, and then you write the type that this expression would have to its left.
But what does that actually mean? Well, think about some C expression. Maybe you take the index of an array, then dereferrence that as a pointer, and finally you call it as a function.
(*(foo[4]))()
What is the type of that expression? Well, it depends on how foo
is declared, right? So how would you declare such a variable? Well, turns out you have your answer right there! You just write that expression right then and there, alongside the type that this expression would have.
int (*(foo[4]))();
Here, we have a variable foo
of type “array of 4 pointers to functions returning int
”.
There are some more wrinkles to that. E.g. When you “convert” from an expression to a declaration, the array index becomes the size of the array. The arguments of a function become parameter declarations. Only a limited number of expressions can be converted this way. But the main take away is: it roughly keeps the shape of the expression.
This might not be news to you. But here is a silly question: How weird can these kinds of declarations become? And what happens when we try to convert some expressions in a way that is unexpected? Can we find some edge cases?
Let’s see. Can we declare an argument as an array? Well, turns out the answer is “no”, but the code doesn’t actually give an error.
int foo(char a[2]);
What happens is that whenever you have a parameter declared as an “array of T
, it will implicitly be converted to a “pointer to T
” for any type T
(and the size will be silently dropped). So the parameter’s type is actually “pointer to char
”
int foo(char *a); /* same as above */
Hmm, but what happens if we make it a multidimensional array?
int foo(char a[4][4]);
Well, in C, “multidimentional arrays” are really just arrays of arrays. So does this become “pointer to pointer to char
”? Well, no. The implicit conversion to pointer only happens at the “outermost layer” of the type. So this really just becomes a “pointer to array with 4 elements of type char
”.
int foo(char (*a)[4]); /* same as above */
int foo(char **a); /* NOT same as above */
(Note that sometimes people will use “pointer to array” as a way to declare C99 VLAs on the heap. It is a neat trick!)
Now, can we declare a parameter as a function? Well, the answer is also “no”. But it isn’t as well known that it will also not give you an error! Well, turns out that parameters with a function type will be implicitly converted to “pointer to function”.
int foo(void f(void));
int foo(void (*f)(void)); /* same as above */
Note that these implicit conversions (of arrays and functions) will happen for effectively all parameters, even those of nested function types.
But wait, can we declare a function that returns an array? Or a function that returns a function? Well, still “no”, but this time we will actually get an error about it.
int foo(void)(void); /* error: function cannot return a function */
int foo(void)[4]; /* error: function cannot return an array */
We can, however, of course, declare a function that returns a pointer to an array or function, though!
int (*foo(void))(void);
int (*foo(void))[4];
You can declare a pointer to any type at all in C, and the way you use pointer types is effectively not retricted in any way (compared to function and array types, which cannot be the type of parameters nor the return type of functions)
As a little bonus question, try to see if you can read the type of this declaration:
int (*foo(int (*foo(char bar[4]))[4]))(int (*foo(char bar[4]))[4]);
There are some other edge cases, like “arrays of functions”. Can you figure out more? What happens when you try to declare a struct field as a function?