11 ms·
The equivalent in C# always made more sense to me (not comparing memory or allocation model between the languages, but simply syntax in relation to a person rea
by cessor 10y ago
The equivalent in C# always made more sense to me (not comparing memory or allocation model between the languages, but simply syntax in relation to a person reading it):
int[] arr = new int[5]; // C#
int arr[5]; // C
The fact that the brackets go on the datatype always made more sense to me, after all, I want to refer to memory of a certain cell size (as indicated by int). I realize that there is a lot of stuff going on when using new, but i believe even in C it should read, because you effectively change the datatype (to be of type pointer, rather than int).
int[5] arr;
But then, I will now duck and get the hell out. This is the same reason why it feels wrong to write:
int *arr;
You're not changing the identifier, you're trying to change the datatype.
In summary, I believe that the syntax to fiddling with pointers in C is very misleading, and this I fully agree with the article, but as many people are accustomed to this notation, 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 will now go and check my garbage collected privilege.
- 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. :)
- posterboy 10y agoJava also gets this right, probably in direct response to the crufty C syntax. > (to be of type pointer, rather than int) No,the array typeis not a pointer, although implicit casts to pointer occur e.g. for passing arrays as function arguments. The practical difference is ... well, I don't know.
- protomyth 10y ago"The Design and Evolution of C++" by Bjarne Stroustrup has a section on alternate declaration syntax that he considered. Compatibility won out but you might find the suggestions interesting.
- bluecalm 10y agoI don't agree with: int[5] arr; being a more readable syntax. The problem is that if you later have: int[7] arr2; It would make sense that arr and arr2 are different types (as the thing on the left is different) while they are the same type and you should be able (hopefully!) to use them as arguments to the same function. On the other hand I agree that: int* arr; makes more sense than more commonly used: int *arr; although it's messed up anyway as: int* a, b, c, d; will result in a surprise.
- SomeStupidPoint 10y agoI mean, int[5] and int[7] would be different types, and lots of bugs happen from passing something besides the appropriate array type to a function. That being said, most languages which disambiguate between int[5] and int[7] provide some kind of polymorphism (and usually store it as a struct of size + data, to enable that). For example: you can define a first function that goes from t[N]->t pretty easily, and it would operate on both int[5] and int[7] (returning an int).
- pasquinelli 10y agoRight, in a language with dependent types there is a type-level difference between int[5] and int[7], but c is not such a language, therefore using a syntax that encourages the mistaken notion that there is a type-level difference between int[5] and int[7] would be misleading.
- Vogtinator 10y agoThere is a small type difference in C, sizeof() of those two types is different.
- Nullabillity 10y agoSaying int[5] and int[7] are the same type because the compiler doesn't enforce the difference is like saying JavaScript is untyped because there is no compiler to enforce types at all. Regardless of what the spec or the compiler says, you the programmer absolutely need to treat them separately. Especially in a language like C, where arrays aren't even self-describing at runtime.
- gpderetta 10y agoUnfortunately this is differently wrong in C# (and many other modern languages). arr here is not an array at all, but is actually a reference to an array and has different semantics from the the corresponding C declaration. This is not just being pedantic: using the same syntax for values and references does lead to confusion. At least C# has actual value types and 'ref' (but not value type arrays AFAIK), in Java this is still being worked on.
- cessor 10y agoI wouldn't agree to call it "wrong", since we're talking about languages that are trying to discourage you from doing your own memory management *(C#, that is, but I'd say this applies to any language running in a VM with gc, really). Thus, in such languages, there shouldn't be a conceptual or syntactical difference between values and references. The fact that C# allows to differentiate them from each other is a leaky abstraction imho, built in to satifsy people form a C/C++ background. I believe that whether this is 'wrong' depends on the problem you're trying to solve. In my day to day work the difference usually doesn't matter, and I am totally happy passing around objects that others would call heavy. I am really happy that I don't have to care about references or values when doing scientific python stuff (pandas, numpy, etc), whereas I am also really happy that this stuff is important to people implementing these libraries.
- gpderetta 10y agoHaving first class references has very little to do with manual memory management. As you said, confusing values with references is a very leaky abstraction, especially when you have mutable data.
- braveo 10y ago> Thus, in such languages, there shouldn't be a conceptual or syntactical difference between values and references. The fact that C# allows to differentiate them from each other is a leaky abstraction imho, built in to satifsy people form a C/C++ background. It's not a leaky abstraction, it's a very specific design choice for performance reasons. Java also has this dichotomy between types, and for the same reason, although I believe they only delineate between primitive/non-primitive.
- itsuart 10y agoIt was always puzzling to me why people stick to char *foo instead of char* foo foo is obviously a pointer to the char, not a char. It has different size and behavior.
- gpderetta 10y agoThat's because [1] char* foo, bar; doesn't mean what one would expect it to mean. [1] as per Rule of Maximum Astonishment.
- itsuart 10y agoI find that char* foo = NULL; char bar = 0; are preferable. Is there any reason one would want to smash N declarations into one?
- gpderetta 10y agoI prefer that as well, but because the other option is possible and sometimes used, a few prefer to put the star close to the name as to avoid mistakes.
- wruza 10y agoOtoh, *foo is obviously a char. Do you put a space between dereference operator and its operand?
- itsuart 10y agoNo, I don't. If foo is a (storage of) pointer to something, to get that something from memory I use dereference operator `* so *foo = 'a'; will write value of 'a' into memory at address that is in foo. type* foo; is declaration and *foo is dereferencing.
- cessor 10y agoI agree. Simply put, it would sense to provide #define CharPointer char* and then declare char c = 'a'; CharCointer p = &c; To me this became clear when I did some C# interop, where IntPtr is a common class, such as: [DllImport("user32.dll")] static extern IntPtr CallWindowProc(WndProcDelegate lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); It's just an object oriented pointer interface. It looks different. I agree: > It has different size and behavior.