Building a C Compiler Type System – Part 1: The Formidable Declarator
blog.robertelder.org
blog.robertelder.org
int *p; // dereferencing "p" gets you an "int"
int **q; // dereferencing "q" twice gets you an "int"
char a[10]; // subscripting "a" gets you a "char"
void (*f)(void); // calling f() gives you "void"
People who write the asterisk next to the type often have a hard time understanding this.At least, that's how I felt until I read your comment. Now at least I understand why using it that way doesn't drive you absolutely insane. So thanks!
the 'type' is pointer-to-int, the 'name' is 'p', hence 'int* p'.
I understand pointers quite well, thankyou....
You should only ever declare one thing per line.
int* p;
int q;
int* a, *b;
The language was intentionally designed so that the same operators with the same precedence levels are used in declarations as well as in normal expressions.It would be great if the language actually worked that way, because that's the natural way to think about it. Unfortunately, it doesn't. In the simple case of a single pointer variable, we can put the star next to the type and pretend that C works the way we think, but it's a fiction.
It seems to be a perfectly rational reaction to say "oh this was a stupid idea, let's not take advantage of it". (which is honestly a great way to approach a lot of C's features)
edit: In reference to the last line: "oh this was a stupid idea, let's not take advantage of it".
It's a weird feature that generally makes the code more difficult to understand but people still use it a lot, similar to multiple declarations on a single line with pointers mixed in.
In ordinary expressions it is a normal binary operator (just like / or &&), but has the lowest precedence and simply evaluates the left-hand side, discarding the result, then (in that order) evaluates the right-hand side, returning its value as the result.
int *p, q;
which says "p is a pointer to integer; q is an integer". That's an argument against writing int* p, q;
because that suggests that both p and q are pointers to integer.And yes, you can write (IIRC)
int q, *p;
(See also http://www.stroustrup.com/bs_faq2.html#whitespace) int *p, *q;
typedef int *intp;
intp p, q;
That just screams that the * belongs to the type and not the variable name. void (*fn)(int*) // fn takes a pointer-to-an-int and gives you void
Wouldn't this suggest that the type this function takes is `int*`?I've always thought the pointer declaration syntax in C was just an oversight and I get around it's ambitiousness by always declaring variables on their own line.
void (*fn)(int *);Why do you think so?
Foo* bar(Baz*, Qux*, int, int*, int**);
I myself do it differently depending on the context. In functions I always stick the asterisk to the type, but if I declare variables, I stick it to the name: Foo* bar(Baz* baz) {
Foo *a, b; Baz c, *d;
...
} int *f(void); // "*f()" returns "int" and "f()" returns "int *"
My habit is to always put a space between the type and the asterisk, regardless of whether there is an identifier: sizeof(int *);
x = (char *) y; number
(address number)
(address address number)
(address array character)
(map (address array character) (list number))
(function number -> number)
etc. For simple types you can replace brackets with colons: address:array:character
But you have full expressiveness if you need it. a: int; // int a
p: ptr(int); // int *p
q: ptr(ptr(long)); // long **q
arr: int[20]; // int arr[20]
arrp: ptr[20](int); // int *arrp[20]
parr: ptr(int[20]); // int (*parr)[20]
fp: fptr(a: int, b: int): long; // long (*fp)(int a, int b)
https://github.com/bbu/quaint-langIMHO the biggest difficulty that beginners face with the syntax is entirely because they attempt to parse it left-to-right and aren't following the precedence; once you realise that the operators (), [], and * in declarations have the exact same precedence they do in the rest of the language, and that expressions like 2 * (3 % foo(i + 4 / x[j])) - 1 are not read left-to-right either, it all comes together and makes perfect sense.
Starting at the identifier (or where it would go, if it was an abstract declarator) and reading outwards following the precedence rules (and recursively applying this to function calls) is the only correct way to parse these declarations, and it is basically what the example program in K&R illustrate.
Thus the "clockwise spiral rule" mentioned in the post is applicable only to certain cases and incorrect in general, as Linus Torvalds explains: https://plus.google.com/+gregkroahhartman/posts/1ZhdNwbjcYF
Edit: upon pondering the example
int f((((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))));
I do not think it is legal in C89/90/99/11, since a parameter-declaration must begin with declaration-specifiers, and the declaration-specifiers cannot begin with an opening parenthesis. (Go to http://www.quut.com/c/ANSI-C-grammar-y.html and start following the rules via declaration->init_declarator_list->init_declarator->declarator->direct_declarator->parameter_type_list->parameter_list->parameter_declaration.)On the other hand, I believe this:
int f(int(((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))));
is legal and the type of the parameter is (pointer to) function returning int, with plenty of redundant parentheses around the abstract declarator. a[1][2][3][4]; typedef x;
Even "better", C declarators can be empty in order to allow for struct/enum declarations that do not list any variables. So you can leave out the variable, add qualifiers, etc: typedef;
const typedef;
typedef const;
It's a really fun syntax in some ways.