5 ms·
> I will now duck and get far away from the internet, in fear of all the hateful comments explaining to me how I am wrong, and apparently just don't understand
by bluetomcat 10y ago
> I will now duck and get far away from the internet, in fear of all the hateful comments explaining to me how I am wrong, and apparently just don't understand the superior beauty of complicated C syntax.
I am going to be that person :-)
One phrase – "declarations mirror use". In a declaration, you use the same set of operators around the declared object that you would use in a normal expression. All of these operators (asterisk, `[]` and `()`) have the exact same precedence and associativity as in the rest of the language. The type specifier(s) on the left is the final type of the expression that you get after applying all of the operators in the correct order as per the precedence/associativity rules.
So when you see:
char *arr[X]
You identify the identifier first: `arr`. Then, because `[]` takes precedence over the asterisk, you say that `arr` is an array (of size X) of pointers to `char`. In other words, the expression `*arr[some_index]` is of type `char`.
- mannykannot 10y agoTo be fair, while declaration mirrors use, the use contains a few traps for the beginner. If you have written some assembler, you will see where C is coming from. It just occurred to me that most early C programmers were probably proficient In assembly.
- mnarayan01 10y agoI don't know if "declarations mirror use" gets you all the way there. You can use: *some_index[arr] (though obviously not saying you should), but you can't declare: char *5[arr]; Obviously there's good reasons for that, but then you have "declarations mirror use, except when there's good reason not to", which basically brings you back to the original question.
- marcoperaza 10y agoFinding an exception doesn't invalidate the explanation. Being able to switch the index and array is more an accident of how C implements arrays than an actual use case.
- chc 10y ago> char *5[arr] Isn't that exploiting a quirk of the operator's definition, rather than how the operator is intended to be used?
- mnarayan01 10y agoI assume you're right, though I would love to see some of the original discussion around it. That said, while I don't think it's a catastrophic point against "declarations mirror use", I do think it's a strike against it. Edit: I guess I'll also note here just for fun that while char [5]arr; seems reasonable enough, char [4][5]arr1; char[5][4] arr2; seems less so.
- dkersten 10y agoI think if all type-information were kept together, it might read simpler: char*[X] arr; You read strictly left to right: char pointer array of size X called arr. Basically, it takes a simple type (char in this case) and for each thing to the right, wraps it in something. You could read it as: given a char, we have a pointer to it, and an array of size X of these pointers. It would be interesting if the use did "mirror" declaration: arr[5]* That is, take element 5 of arr and derefence it. Just some random thoughts...
- bluetomcat 10y ago> You read strictly left to right That wouldn't play well with pointers to arrays and pointers to functions: int (*pa)[X]; // int(*)[X] pa; int (*fp)(void); // int(*)(void) fp; Here is how I designed the type declaration syntax in my own language, Quaint (https://github.com/bbu/quaint-lang https://github.com/bbu/quaint-lang): pa: ptr(int[3]); // int (*pa)[3] fp: fptr(): int; // int (*fp)(void) arr: int[5]; // int arr[5] p: ptr(int); // int *p pp: ptr(ptr(int)); // int **pp arrp: ptr[5](int); // int *p[5] You basically have the identifier first, and then a recursive expression which uses mostly function-call-like expressions in order to nest recursive types.
- fredmorcos 10y ago> char pointer array of size X called arr That does not even English.
- deleted 10y ago[deleted]
- bogomipz 10y agoI've heard this phrase - "declarations mirror use" many times before but it just doesn't click for me for some reason. Who's use? The compiler's or the programmer's? Can you elaborate? Sorry if this silly question but I've scratched my head enough on hearing that phrase that I thought I would ask. Cheers.
- bluetomcat 10y agoThe meaning of "declarations mirror use" is that the programmer can use the declared variable in the exact same way as being declared (by applying the exact same operators): int **p; // declares "p" as a pointer to a pointer to int int x = **p; // the "**p" expression mirrors the declaration and its type is "int" This is true even for functions: int *f(int a); // declares a function which takes an int and returns a pointer to int int res = *f(3); // the "*f(3)" expression mirrors the declaration and its type is "int" Arrays are obvious when looked that way: int arr[5][5]; int elem = arr[1][2]; // the "arr[1][2]" expression mirrors the declaration
- bogomipz 10y agoThanks for the clear explanation. This makes sense. I do have a tangential question. I have read that the reason for this declaration syntax is that it was easier form compilers and compiler writers to parse. How does this declaration syntax facilitate parsing exactly? I fail to see why this is.
- bluetomcat 10y agoThe advantage is that you don't introduce any new operators or syntactic forms which would only be used in declarations. This means a smaller set of tokens and a smaller set of syntactic forms. Being able to parse an expression like (*arr[i])(42) means that you can use pretty much the same machinery to parse int (*arr[SIZE])(int a)
- bogomipz 10y ago
- contravariant 10y agoSurely it would still mirror use if the operators were applied to the type, rather than the identifier? Preferring 'char ٭arr[X]' over '٭char[X] arr' seems arbitrary to me. I see no reason the 'declaration mirror use' principle can differentiate between the two. Personally I prefer the latter since it makes it easy to separate the type from the identifier.
- bluetomcat 10y ago> Surely it would still mirror use if the operators were applied to the type, rather than the identifier? The "type" is actually a list of storage class specifiers (static, extern, auto, register, typedef, _Thread_local), type qualifiers (const, volatile, restrict) and type specifiers (int, char, float, double, signed, unsigned, long, short, void). Imagine the soup of keywords that would have to be at the deepest level of the expression: static const volatile unsigned char *arr[X]; // vs *static const volatile unsigned char[X] arr;
- Nullabillity 10y agostatic, const, and volatile modify the variable itself, not what values it's legal for it to contain. So the outcome should be: static const volatile *unsigned char[X] arr; Or, because signedness should be a property of the type, not an arbitrary modifier: static const volatile *char[X] arr;
- fredmorcos 10y ago'char ٭arr[X]' over '٭char[X] arr' What is this dirt after char and before char? This wouldn't even compile.
- bluetomcat 10y agoHN converts asterisks to italics and I too couldn't find a way to escape them, except when surrounded by backticks `*`. Not a C-friendly discussion forum :-)
- sqeaky 10y agoI have been writing C and C++ for a little more than 10 years now and this is the first I have heard "declarations mirror use". I understood that was the case, but why is the phrase important. To me understanding the actual types involved is more important than getting the declaration to look some specific way. (So I always jammed the star next to type and did whatever else I though would most ease expressing types) I am also the kind of developer who who have declared it as: std::array<std::string, X> arr; or std::vector<std::string> arr; depending on if X was known at compile time. Because I want to give the compiler as many chances to call out my mistakes as possible.
- cessor 10y agoThank you for clarifying. I have never heard of "declaration mirrors use" (DMU) before. Also thank you for explaining it in a non-hateful way (which isn't always the tone when touching religious matters). Next up: Tabs vs. Spaces ;) This sounds really odd to me. I can see, and others have pointed this out in the comments, too, that DMU might facilitate building a compiler for such a language and reuses several operators. However, I am not sure whether I like this goal from a person-centric perspective. Experts might be used to matching declaration and use structures in their code, maybe to make it more 'symmetric', but I am sceptical whether this match is actually quite 'expensive' when developing code with many people of different skills. Use and declaration are differnt concepts, and as such should have different representations (i.e., syntactically), otherwise you might introduce synonym defects (i.e., ambiguities, where it becomes hard to understand what somebody means). I found that some of my colleagues and students had a hard time to learn what pointers are about (and I believe this is a fairly common phenomenon, that people find pointers in C hard to grasp), because the asterisk is used both in declaration and in dereferencing. I found that some of my students got the concept more easily after I introduced a couple of macros: #include "stdio.h" #define IntPointer int* #define value_of(ptr) *ptr #define address_of(v) &v int main() { int a = 42; IntPointer p = address_of(a); printf("%i\n", value_of(p)); return 42; } The above code has no other purpose than to separate the concepts verbally, so that they can be reasoned about explicitly. This can be achieved in other ways, for example, in C++ I tend to use templates or classes to abstract the pointer syntax away. Of course, I am aware that using classes introduces overhead and this technique is not necessarily feasible when you need as much performance as you can get. This also underlines my original point to place the asterisk on the datatype rather than on the identifier, because the #define wouldn't make sense otherwise. In summary, I was unaware that DMU was a design goal, but I suspect it to make the language more difficult to learn, code harder to read, altough this effect might not be an issue for experts. I am not trying to convince anybody that DMU is "bad", but I am interested in the actual properties. Everybody making claims for readability owes the community an empirical evaluation. Disclaimer: I am currently working on a research project about program comprehension, including some eyetracking and FMRI work, providing exactly such evaluations. :)