3 ms·
It's not inconsistent. There are two things in a declaration: a type (here int) and an expression (here *x). If you add commas you can have multiple of the latt
by jstimpfle 2y ago
It's not inconsistent. There are two things in a declaration: a type (here int) and an expression (here *x). If you add commas you can have multiple of the latter. That's it. Stop thinking of the star as part of the type, it isn't. Leave it with the expression to the right, and thinks make sense .
It's why I always cringe when I see
int* x;
as opposed to
int *x;
There is a reason why K&R do it that way.
- jandrese 2y agoThe idea that the * isn't part of the type has always felt completely wrong to me. The entire concept of an expression modifying the type doesn't provide any benefit that I can see. The only thing the expression should be doing is giving you the chance to assign the name to the new variable you're creating. Once you define the type, which might be a pointer or not, it should be fixed. I get that this is a pretty fundamental disagreement between me and Knuth & Richie, but I think history has shown that this idea of splitting exactly one aspect of the type (if the variable is direct or a pointer) was a mistake. It's been the cause of a lot of bugs and the cases where you want to define both direct and indirect types on the same line basically don't exist. For clarity you should split those definitions up regardless of what the syntax allows. There are basically no cases where you should be writing the following code even if it is technically correct: int *pointer, direct;
- jstimpfle 2y agoI never use comma, I always define one variable per line. Much easier to edit and read. However, the way it works is certainly not inconsistent. As to your other concerns, it makes perfect sense. int WHATEVER; read as "WHATEVER is an int" and you can work backwards from there. There is no more type syntax than that. The thing is, it's much easier to look at stuff this way since otherwise you'll have to shift back and forth between 2 different syntaxes (types and expressions) all the time. I've been looking a lot for a better more algebraic syntax too. All syntaxes I've found have lost on two axes _in the common and simple cases_ - low cognitive overhead - terseness Yes, the principle of declaration follows usage is already stretched to its limit, and C++ has certainly overstretched it. But still for the bread-and-butter use cases, the "better" syntaxes seem to always lose on those 2 axes.
- zzo38computer 2y agoI only declare multiple variables in one line if it is a "simple" type, without pointers, arrays, functions, etc. Also, a code such as: int*x=y; will define the initial value of x and not of *x. For this reason, a space before the asterisk can be confusing in this way. And, for the reasons described in another comment, putting the space after the asterisk is also confusing. So, I find it clearer to omit both spaces. When I require more complicated types, I will generally use typedef (or typeof) instead of having to deal with the confusing syntax of types in C.
- shrimp_emoji 2y agoKernighan & Ritchie :p The rest of your post is perfect and objectively (subjectively) correct. While I fortunately knew that `int* x, y` means `y` is not a pointer, I always assumed it was some kind of capricious oversight of the compiler. I had no idea the rationale was "the expression `*x` declares `x` to be a pointer to the type west of it". That strikes me as so weird! XD
- delta_p_delta_x 2y ago> There is a reason why K&R do it that way. This was and remains a bad decision, full stop. 'Pointer to something' should have been a parametrised type rather than C's weird syntax of overloading operator*. And I won't accept the argument that C was too old to have parametrised types: ML, Lisp, and many other functional-first languages developed alongside C and had them. We've just stuck with C because it got popular because of UNIX and then Linux. I avoid raw pointers whenever I can when I write C++, and I yearn for a language that exposes pointers with a rich and expressive type system instead of making them glorified 64-bit unsigned ints. Pointers ought to be one-dimensional unsigned parametrised affine space types[1] (note: not affine types, which are completely different[2]), with several implications: - comparing pointers to different types should be invalid - subtracting pointers to the same type should return a signed 'pointer difference' type - adding pointers to the same type should be invalid - Get the address contained by a pointer with Ptr<T>::address() [1]: http://videocortex.io/2018/Affine-Space-Types/ http://videocortex.io/2018/Affine-Space-Types/ [2]: https://en.wikipedia.org/wiki/Substructural_type_system#Affine_type_systems https://en.wikipedia.org/wiki/Substructural_type_system#Affi...
- jstimpfle 2y agoWould've, should've, could've. Programming languages are a huge waste of time. It's a domain with lots of opportunities for bike-shedding, and then for hard-core R&D too. But programming is still engineering, simple & easy that gets the job done wins over needlessly complex. And yes, inertia is real too, but if a programming language was dramatically better at what C does best, I wouldn't still start new code in it. When you're doing systems programming, pointers being "glorified" 64-bit ints is pretty much what you need. When your requirements hit reality, all the nice abstractions break down quite easily. > I avoid raw pointers whenever I can when I write C++ Empirically, this is the path to madness. > - comparing pointers to different types should be invalid It produces a warning on my C compiler and an error on C++ compiler. Note that there _can_ be pointers containing equal addresses with different types -- a struct and its first member have the same address. > - adding pointers to the same type should be invalid I think it is, or what exactly do you mean? > - subtracting pointers to the same type should return a signed 'pointer difference' type it does, that type is called ptrdiff_t, which is a signed integer type. > - Get the address contained by a pointer with Ptr<T>::address() But you didn't want them to be glorified 64-bit ints? Well, because of pointer arithmetic, which is a practical necessity, they kind of are, but then again they're not because C tries to abstract from that. You can kinda unwrap the pointer abstraction by casting to the (optional) uintptr_t type. The thing is, not all machines necessarily have one flat address space that contains everything from functions to all data. Pointer representation isn't necessarily uniform -- e.g., you can have a bigger type for void-pointers that contain type information too, this way you could do run-time type checking.
- nuancebydefault 2y agoTo me int* x is preferred, here's why... I read it like 'IntegerPointer x' or 'x is an integerPointer' or 'x is a pointer to an integer' It rhymes with the following : 'MilkGlass m' reads as: 'm is a MilkGlass' or 'm is a Glass of Milk'
- jstimpfle 2y agoYeah but this quickly breaks down -- it's not how it "really works". What about arrays for example? int* x[200]; You can fight all you want, but C doesn't have type syntax. What you're looking at is expression syntax.
- nuancebydefault 2y agoI would read that x is a collection of 200 intPointers. Is that not correct?
- jstimpfle 2y agoWell yes but there is something off because 200 is not with the "type" anymore. Now do a pointer to an array of 200 ints.
- nuancebydefault 2y agoOh, now i think i know what you mean. 'a is of type one hundred intPointers' should, to be more consistent, be written as (int*)[100] a; I'm not even sure whether that works, never tried it. The fact that arrays are automatically pointers is very confusing for beginners, especially multi dimension arrays are brainbenders in that respect.
- teo_zero 2y agoOf course you can read it. That was just an example to show that C declarations can't always be decomposed into a left part which is the type and a right one which is the variable. It works on the simple case of a pointer, because * is a prefix operator, but it fails on arrays, because [] is postfix. int * <---> foo; int [] <---> foo; // ugh no! The latter would work if C had been designed differently, with operations defined on types. But it was designed with operations defined on values, not on types. There's not a way to express "foo is a pointer to int", you must say "when you dereference foo you get an int" (in fact, * means dereference, not pointer). Imagine if English had not a word for "son". To say Bob is Adam's son you would say "Bob's father is Adam". To tell the truth, C does have the dual of * that is &. If operations on types were defined (and they are not!), then we could have a syntax like this: &int foo; In any case, we wouldn't solve the issue with arrays, because, while [] is the operator that, given an array, returns one element of it, its inverse operator doesn't exist. It's not difficult to imagine a different language with a rich set of operators defined for types. We could have &T for "pointer to type T", Tn (note the infix , as multiplication) for "array of n elements of type T", and so on. Then declarations could be easier: &int ptr; float*3 rgb; &int*8 foo; // array of 8 ptr &(int*8) bar; // ptr to array See how we are using full expressions including parentheses with types? At this point, you would also move the variable to the left and the type to the right, and maybe add a colon for clarity: ptr : ∫ rgb : float * 3; foo : &int * 8; bar : &(int * 8); But this is another language, not C.
- jayd16 2y agoSo what about smart pointers? Is it not inconsistent that smart pointers are a type but raw pointers are not?
- jstimpfle 2y agoRaw pointers "are" a type too. It's just that there isn't really syntax to describe the type, at least in the original declaration syntax. Note that structs and typedefs that you define are proper type names, you can switch out "int" for them. You can easily "typedef int *intPtr;" and then merrily go "intPtr myptr". (I think cast type-specs might be a bit of an exception here were the rule breaks a bit -- because they don't "use" a name in the cast "expression". I don't intimately understand how those work. The whole thing is an idea that was stretched to the limits, especially with C++ where consistency has pretty much been given up).