<specifiers> <declarator> {, <declarator>, ...} ;
The star is a type-deriving operator that is part of the <declarator>, not part of the <specifiers>!This declares two pointers to char:
char *foo, *bar;
This declares foo as a pointer to char, and bar as a char: char* foo, bar;
We have created a trompe l'oeil by separating the * from the declarator to which it begins and attaching it to the specifier to which it doesn't. char* foo, bar;
So that's why I've had so many problems understanding C. I come from the Pascal world, where a type specification is straightforward.(There were suggestions back in the 90s that to make C easier to parse for humans (and not-coincidentally simplify the compiler grammar) this should be `foo, bar: float*;` and your model of pointerness could actually be true. Never got much more traction than some "huh, that would be better, too bad we've been using this for 10 years already and will never change it" comments :-) (with an occasional side of "maybe use typedefs instead")
If you value your codebase anyway.
Defensive programming that extreme reminds me of the behavior I learned to avoid pissing off my drunk dad.
Personally i used "float *foo" for years until at some point i found "float* foo" more natural (as the pointer is conceptually part of the type) so i switched to that, which i've also been using for years. I've worked on a bunch of codebases which used both though (both in C and C++) - in some cases even mixed because that's what you get with a codebase where a ton of programmers worked over many years :-P.
I do tend to put pointer variable declarations on their own lines though regardless of asterisk placement.
(and of course there is always "float foo[42]" to annoy you with the whole "part of the type" aspect :-P)*
One important (and beautiful) thing to understand about C is that declarations and use in C mirror each other.
Consider the same type written in Go and C: array of ten pointers to functions from int to int.
Go: var funcs [10]*func(int) int
C: int (*funcs[10])(int)
Go's version reads left to right, clearly. C version is ugly.
But beautiful thing about C version is that it mirrors how funcs can be used:
(*funcs[0])(5)
See how it's just like the declaration.
Go's version doesn't have this property.
So, now about the *.
Usage of * doesn't require spaces.
If p is a pointer to int, you use it like this: *p
And not like this: * p
And since type declarations follow usage, therefore "int *p" makes more sense.
There is also a good argument about "int *p, i". In the end, these usages follow from how the C grammar works.
There are many more musings about that on the web, but here is one of my favourites: https://go.dev/blog/declaration-syntax.
Edit: HN formatting.
We might wish for the C declaration syntax to be <type> <name>[, ...], but it’s not: it’s <specifier>[ ...] <declarator>[, ...], where int, long, unsigned, struct stat, union { uint64_t u; double d; }, and even typedef are all specifiers, and foo, (foo), (((foo))), *bar, baz[10], (*spam)(int), and even (*eggs)[STRIDE] are all declarators (the wisdom of using the last one is debatable, but it is genuinely useful if you can count on the future maintainer to know what it means).
Everybody is free to not like the syntax, but actively misleading the reader about its workings seems counterproductive.
Get with the program: types on the left, names on the right, one declaration per line.