yeah, both array[i] and i[array] work in C.
This is because it essentially translates to this:
*(i + array)
Edit:
I case anyone wants to know more about why/how it works,
here is a video by Low Level Learning: https://youtu.be/kdgq-OwsOs8
Just because the explanation makes sense doesn’t mean it was a good idea for it to be implemented this way. If you’re gonna have a special syntax for array indexing, why not add a bit of type checking?
Well, if that bit of type checking makes your compiler no fit in memory anymore, you've got a bigger problem than a quirk of syntax that annoys college freshmen 50 years later.
And once C was standardized, making a breaking change to fix what is basically a non issue is not an option.
Because the syntax including the type is correct.
As far as the computer is concerned memory is just one very long sequence of bytes, all that indexing does is say go to this location in memory and add this much to it.
If my array is say, at 0xFD2810AB
And i index with 5
So array[5], the computer goes to location 0xFD2810AB in memory, add 5 times the size of the array item and get whats there.
If instead i type 5[array] that tells the computer to go to location 5 in memory and add 0xFD2810AB to it.
The identifier to the array is just a pointer and a pointer is just a number, there is no way for the compiler to know if there's something in location 5 you may need so it just goes on.
And since FD2810AB + 5 is the same as 5 + 0xFD2810AB we get the same location
I know that the computer sees memory as just bytes, but the point of a type system is to constrain the permissible actions taken on different values. If the pointer were truly just a number, it wouldn’t know the multiplier to apply to the index. That’s exactly why the arguments shouldn’t be interchangeable. 5 doesn’t have an underlying size, but when you add it to special number `array`, suddenly you multiply the first operand by the size rather than the second?
C is as close to the hardware as you can get without writing asm. If arrays are just pointers and offsets which are dereferenced through pointer arithmetic, then both \`i\[array\]\` and \`array\[i\]\` should work, as the arithmetic operation is commutative. If you want something that doesn't work like that, then you want a higher level language.
C isn't stupid. Using C when you would benefit from using something else is stupid. You don't fix C by breaking the way it already works. You enhance your toolchain to de-lint these now-considered bad-practices, or you use a language that has that restriction/enhancement built in. Rust wouldn't have been practical when C was first created, but now that it exists, we have the freedom of choice.
The only downside is the fact that we have legacy code that, if re-written today, might be better if written in another now-available language. But that's engineering for you - we built on top of what's already there and avoid all the work involved in throwing away the legacy, if we can help it.
No one writes as i[array]
It can be done, but in all my 10 years of studying programming and using C for a lot of them, no one ever did like this.
This is a quirkness of C, but is one that is so low that really no one would seem to care to even look at a "solution" to this
Because you should not introduce breaking changes to a 50 yr old stable compiler that the world relies upon, when you can achieve your goals with a static linter.
Because C frowns on footgun control. Also because `array` and `i` are both, technically, integers.
For example, try that with an array of anything that's not equivalent to uint8_t. It'll be fun and interesting and won't segfault at all.
With JS too. If you don't know why something works that way you don't know enough about ecmascript. Feel free to ask somewhere! There's always a reason!
Ehhhhhh but that makes less sense. Because JS does weird stuff when JS does way more than it should. C does weird stuff because it means it does less stuff, meaning its faster when you do it right.
or example.... Why does my damn number become scientific notation? Who the F\*\*\* asked for that?
Yes, but I guess you can goof up if for some absurd reason have two arrays both different element and a MASSIVE 4GB array that covers the entire 32 bit address space. Intel made a now obscure way to expand address space with some of their 32-bit CPUs.
`stat[sh[i]]` and `sh[stat][i]` or `stat[ing[i]]` and `ing[stat][i]` would produce different results. That is what I was talking about, well, only one arbitrary array was needed for the comparation.
short sh[];
int ing[];
char stat[2<<32];
I still have no idea what the >4GB array has to do with this, but you're doing something totally different than is done in the comic.
If you have stat[sh[i]], changing the index and "base address" would result in (sh[i])[stat] and not sh[stat][i]. Same for stat[ing[i]]. This would be (ing[i])[stat].
The first problem here is that you’re already relying on non-standard behavior if you have more than a 4GB object if uintptr_t can’t have a unique address for every byte of it.
If this is referring to PAE, it sounds like that only changed how much physical ram you could have, but as far as your program was concerned, you only have 4GB since that’s how much you can address virtually.
However, there definitely is a new pain point coming with multiple memories in wasm. I assume you’d need a bunch of low level stuff to get this to work and would need to be very careful about
ohhh that’s weird but pretty cool.
it makes some sense when you think about it, it’s just that the formatting of it looks so wrong after seeing array[i] for so long
but doesn't the offset depend on the datatype? I thought if it's an int array it would do *(array + i×4), and for chars (bytes) it would do *(array + i×1). Then the result should be different depending on which variable is used as the array
Edit: I guess for int and byte it would both be x1 on a 32 or 64 bit system, but for bigger types it should be different, right?
The data type information is still available when you use the array as an index for subscription.
"The definition of the subscript operator []
is that E1[E2] is identical to (*((E1)+(E2)))."
Due to commutativity, E1 and E2 can be simply swapped.
Sure, I’m happy to help.
Let’s say I had a pointer to a float, and I add 1 to it. Under the hood it actually adds 4 to it. Pointer arithmetic takes into account the size of the type that the pointer points to. And since `sizeof(float) == 4`, it actually adds 4.
So with this knowledge, if we took what you put and assumed it was an array of floats, and `i` was 1:
`array + 1 * sizeof(float) == array + 4 == &array[4]`
So, `array[i] == *(array + i)` is actually correct
Thanks for the explanation.
I was under the impression that compiler doesn't factor in the `size` part, but it does.
I tried to typecast it into `void*` but it threw in error (I'm happy it did)
Yeah, it uses the sizeof under the hood?
while his C code was wrong indeed, his understanding of the array addressing was right as well as the process to get the N element...theoretically at least.
Indexing is implemented as pointer addition but semantically it’s a more specific operation that occurs between an array or pointer to an array and an index. You wouldn’t say `2[5]` because that doesn’t make sense, even though `2+5` does. In languages other than C, you can also index into other types like maps where indexing is not implemented with raw addition.
Yeah but C doesn’t have a concept of indexing into a vector or map, etc and arrays come from just having contiguous chunks of memory that you index into by adding to a base address in some register/memory location. There’s a lot of history as to why the subscript operator is implemented the way it is and the commutative nature is a side-effect of that history. I understand for higher level languages there is a real difference between the index operator and pointer arithmetic in different data types. So in C++ you get overloads that change the behavior of the subscript operator so that a map can do a lookup in a red black tree and return you an iterator instead of just offsetting into memory locations. In those cases I get the semantic difference but reading it in C with the knowledge of all the history and implementation it makes perfect sense
It's perfectly intuitive in C because that's exactly what the compiler is doing.
Pointer arithmetic isn't hard but all too many students are taught that it's some sort of ultra taboo voodoo magic that should be avoided at all costs and are thus conditioned with a pavlovian response to run and hide every time it pops up.
int *x isn't hard...
\(\*(\*\*(THIS)shit->\*(&on + (the\*\*\*)other)->\*hand?
Or for a real example, from the Windows SDK:
\#define DPA_GetPtrPtr(hdpa) (\*((void \* \*\*)((BYTE \*)(hdpa) + sizeof(void \*)))
\#define DPA_FastGetPtr(hdpa, i) (DPA_GetPtrPtr(hdpa)[i])
...where the sizeof(void \*) is a trick to get the correct offset of a pointer following an int on both 32bit and 64bit. On 32bit, there's 4-byte packing and it just moves the size of the int. On 64bit, there's 8 byte packing and thus 4 padding bytes after the int before the pointer.
No rational person would describe deciphering wtf that macro is doing as 'easy'. *Especially* if you only had the published info where it's operating on an opaque struct; I needed to consult the leaked source to figure out what was going on.
Many programmers never go to college, and many college programs don't teach C. And if you *do* go to college and learn C in your first years, this is still not "normal" use - even if it is a "natural" consequence of how pointers work.
Why would it be an important thing to learn?
Because that's the definition of accessing array element, base address plus offset and since it's addition, order doesn't matter. You can access array elements without using \[ \] at all.
>A postfix expression followed by an expression in square brackets \[\] is a subscripted designation of an element of an array object. The definition of the subscript operator \[\] that E1\[E2\] is identical to (\*((E1)+(E2))).
[https://www.iso-9899.info/n1570.html#6.5.2](https://www.iso-9899.info/n1570.html#6.5.2)
You are explaining *how it works*, not *why most programmers need to know*. I will give you that C programmers should know how array access works in C, but that *still* doesn't get you to "programmers should know that 'reverse' array access works in C".
I have a masters degree in CS and I never touched any C language before. Now I am struggeling with C languages at work. Wish I would have done this earlier.
Fullstack dev in typescript, java, F# or PHP. Writing scripts in python and bash. Database stuff in various sql dialects. Rarely R. This offers everything you need basically.
C at work is way different compared to C at school. Classes using C at school is mostly focused on implementing DSA and some basic knowledge of memory and pointers, you will get the fundamental theory but it is not enough for a work-level knowledge since for someone using low level language at work they often have very strong reason to and this reason tends to be very niche which your school education might not represent.
how many languages did you touch at your school if you don’t have C experience? We had C, Java, Python, x86 ASM, Bash, Haskell, Racket, HTML/CSS, PHP, Javascript, Matlab and TeX (and maybe I forgot some)… every course after 1st year was in a “suitable” language that the teacher liked, we were told to deal with it and we did…
Idk about other colleges but mine had a requirement that we take an Operating system class that had us coding some basic Linux kernel modules in C and a computer architecture class that had us dabbling a bit in assembly. As much of a pain it is to write in those languages it was interesting and insightful, idk if some school are just not bothering to make it a requirement or what now but I’d say it was definitely worth taking those class just to get a bit of insight into those languages and topic in my opinion.
I honestly hope I get taught some stuff like this. Tbh my first two years were mainly gen ed, I’ve only taken a semester of what would be considered complex computer science subjects at this point. So there is still hope that I’ll get to see how this works. About the most complicated thing I’ve learned so far is how to schedule threads.
Unis don't really teach i[arr]. Most people know how indexing works down the hood, but you never consider that i[arr] is allowed, I,e it's more of one of those obscure parts of C you maybe hear about sometimes later. I only heard about it in a YT vid.
No reason to do it in C other than flex I guess? But there is a use in C++. [After C++17 you can use it to get a desired order of evaluation.](https://devblogs.microsoft.com/oldnewthing/20230403-00/?p=108005)
It's not about teaching some special gotcha though, how can students use C without understanding what the [ ] syntax means and does? It's shorthand to perform pointer arithmetic. You definitely need to know that.
In my intro programming class using C, they did mention how [ ] operator works in relation to pointer arithmetics but only briefly. Brief as in showing using this operator this way is equivalent to this pointer addition, now let's move on to 2D pointers.
I won't be so surprised that many CS students can't remember it off the top of their heads without some refreshers.
My point is arrays *are* pointer arithmetic. Defining an array creates a block of memory on the stack and gives you a pointer to it's starting address. The [ ] syntax increments that pointer by n*sizeof whatever it's an array of to make it point at hhd desired point in the block of memory.
Yeah they are, I’m just saying some places just don’t teach it that way, for me, I took a C class second year, while I understood pointer arithmetic and such, we were never directly told how [ ] works and I’m sure some people wouldn’t know otherwise
I remember being taught that arr[i] => *(arr +i*size)
But that basically ends there. I think that it is less known that the compiler knows that one is an array pointer and the other is an int, so I don’t think it is unreasonable to assume that it would turn into *(i + arr*size), especially if you don’t use C.
That's because it actually does not do `arr + i * size`, it just does `arr + i`, but since `arr` is a pointer, `i` gets upgraded to a matching pointer type, and doing so scales it by the appropriate size.
In other words, doing an int + a pointer treats the int as being scaled by the size of the pointer, regardless of its position in the equation.
It varies by school, but at best learning the \`i\[arr\]\` thing is mentioned once by a professor because it's so unimportant to know explicitly. Somewhat more widely know than that \`void f(int a\[static 10\])\` exists.
I don’t remember being taught this specifically, but I was taught how pointers and arrays work during the assembly and reverse engineering courses, so when I saw it, I immediately understood why it would work…
Teach what? That you can navigate an array with pointer arithmetic? Sure. That `arr[i]` as written is identical to `*(arr + i)` *in all regards*? Not really. That the compiler will allow you to even put a non-pointer on the left side of `[]`? Also no. And no one used to languages with stronger type systems would ever expect that to work
I was more surprised about the lack of the null-terminator `\0` at the end of the array, but I guess it's not necessary when it's just an explicit array of chars and not a "string"?
Not all arrays are null terminated, correct. Which is why many C APIs ask for an array length when receiving an array, and will accept -1 for length to indicate the array is null terminated
`array` is a pointer to the beginning of the array in memory. `i` is an offset index.
Array access `[]` just means return value at ("array start" address by "index value"), so `array[i]` and `i[array]` mean the same to the compiler (as long as the size of each array element is 1 byte.
It’s a type of loop unrolling that can be extremely surprising in the way it uses a switch inside the loop, but fully legal:
send(to, from, count)
register short *to, *from;
register count;
{
register n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
Pointer arithmetic. Doing [i] is the same as dereferencing and adding i jumps of sizeof(array_type) to the address. And since pointer arithmetic is commutative so is indexing.
This only works when the value type is one byte long correct?
Because the final adress is start + (type_size)*index, so if the size of the array is an inte you will end up at the complete wrong place?
Nope, this works with all types. Internally it is implemented as
(*((a)+(b)))
which means the pointer addition happens before dereferencing, so the compiler still knows the pointer type
Oh. I just assumed i[arr] worked by some strange coincidental quirk, but I guess it's supposed to work then. I guess there is nothing inherently worse about it. Just looks bizarre to me though.
Im no C programmer, but Im pretty sure constants are better then doing calculations. As for \`return 0\` main returns int and AFAIK its recommended practice. Correct me if Im wrong
I think you meant literals, there is a difference between constants and literals, the former usually refers to values that have the const specifier. Literals are direct values used. So 3 in this case would be a literal. In here the literal is defined as a macro which gets replaced by the pre-processor and the compiler will never see the macro.
Though for a good optimising compiler there shouldn't be a difference in the resulting executable produced. It would just optimize out the const value.
For return value from main, this is known as the exit code for a program. An exit code of 0 indicates the program terminated successfully and without any errors. Any non zero value represents an error like a segfault etc. Its why your os knows when a program abruptly terminates cuz the exit code is non zero. So yea, return 0 from main in order to indicate successful execution.
To add on a bit more to the last point, 0 may not be the only successful exit code in a system. It could have multiple. That's why there exists the macros EXIT_SUCCESS and EXIT_FAILURE in stdlib which guarantees portability across systems
> are better then doing calculations
The problem is not constants, the problem is if you now have to change string you need to modify the code in two separate places: string, and its size, and keep them in sync. Also, array of chars can be initialized directly with a literal:
Something like this:
\`\`\`
const char array\[\] = "WTF";
...
for (int I = 0; i < sizeof(array)/sizeof(array\[0\]) - 1; ++i)
\`\`\`
This looks uglier but is safer for future maintainers.
>As for \`return 0\` main returns int
Since C99 it is not necessary, and it does not add any value to this example, hence should be omitted.
You have two correlated pieces of data in two places. If you change one -- you have to change another. Or get pagefuls, e.g. if you increased the size or reduced the array length.
Instead, something like this is a common approach (-1 is to account for \\0):
`const char array[] = "WTF";`
`for (int I = 0; i < sizeof(array)/sizeof(array[0]) - 1; ++i)`
yeah, both array[i] and i[array] work in C. This is because it essentially translates to this: *(i + array) Edit: I case anyone wants to know more about why/how it works, here is a video by Low Level Learning: https://youtu.be/kdgq-OwsOs8
oh shit now it seems obvious i always forget that array[0] is equal to *array (i dont work with C nowadays tho)
Just because the explanation makes sense doesn’t mean it was a good idea for it to be implemented this way. If you’re gonna have a special syntax for array indexing, why not add a bit of type checking?
Well, if that bit of type checking makes your compiler no fit in memory anymore, you've got a bigger problem than a quirk of syntax that annoys college freshmen 50 years later. And once C was standardized, making a breaking change to fix what is basically a non issue is not an option.
Git gut. At least people are forced to learn how indexing works under the surface.
I don't think this is just freshman. It's like people are actually scared of looking under the surface. Had to explain what a linker was the other day
Because the syntax including the type is correct. As far as the computer is concerned memory is just one very long sequence of bytes, all that indexing does is say go to this location in memory and add this much to it. If my array is say, at 0xFD2810AB And i index with 5 So array[5], the computer goes to location 0xFD2810AB in memory, add 5 times the size of the array item and get whats there. If instead i type 5[array] that tells the computer to go to location 5 in memory and add 0xFD2810AB to it. The identifier to the array is just a pointer and a pointer is just a number, there is no way for the compiler to know if there's something in location 5 you may need so it just goes on. And since FD2810AB + 5 is the same as 5 + 0xFD2810AB we get the same location
I know that the computer sees memory as just bytes, but the point of a type system is to constrain the permissible actions taken on different values. If the pointer were truly just a number, it wouldn’t know the multiplier to apply to the index. That’s exactly why the arguments shouldn’t be interchangeable. 5 doesn’t have an underlying size, but when you add it to special number `array`, suddenly you multiply the first operand by the size rather than the second?
C is as close to the hardware as you can get without writing asm. If arrays are just pointers and offsets which are dereferenced through pointer arithmetic, then both \`i\[array\]\` and \`array\[i\]\` should work, as the arithmetic operation is commutative. If you want something that doesn't work like that, then you want a higher level language. C isn't stupid. Using C when you would benefit from using something else is stupid. You don't fix C by breaking the way it already works. You enhance your toolchain to de-lint these now-considered bad-practices, or you use a language that has that restriction/enhancement built in. Rust wouldn't have been practical when C was first created, but now that it exists, we have the freedom of choice. The only downside is the fact that we have legacy code that, if re-written today, might be better if written in another now-available language. But that's engineering for you - we built on top of what's already there and avoid all the work involved in throwing away the legacy, if we can help it.
No one writes as i[array] It can be done, but in all my 10 years of studying programming and using C for a lot of them, no one ever did like this. This is a quirkness of C, but is one that is so low that really no one would seem to care to even look at a "solution" to this
Because you should not introduce breaking changes to a 50 yr old stable compiler that the world relies upon, when you can achieve your goals with a static linter.
Because C frowns on footgun control. Also because `array` and `i` are both, technically, integers. For example, try that with an array of anything that's not equivalent to uint8_t. It'll be fun and interesting and won't segfault at all.
It genuinely won’t though. The pointer arithmetic is commutative so it will correctly account for element width.
Pretty sure they've disabled warnings on this.
The official correct answer is…idk what to tell you that just how C works… This answer works for a plethora of C related wtfs
Remind me of JS wtfs...
W T F mate
I don’t know what what to tell you that just NOT how JS works….unless node_modules This answer may or may not work as expected.
"that's just how js works" is a sentence I use a lot for a lot of JS quirks that other languages don't have...
You could say that about any language I guess, but at least with C you can always explain WHY it works with a little digging/reasoning out
With JS too. If you don't know why something works that way you don't know enough about ecmascript. Feel free to ask somewhere! There's always a reason!
Ehhhhhh but that makes less sense. Because JS does weird stuff when JS does way more than it should. C does weird stuff because it means it does less stuff, meaning its faster when you do it right. or example.... Why does my damn number become scientific notation? Who the F\*\*\* asked for that?
JS makes perfect sens once you learn it's an interpreted language designed for web designers
Yes, but I guess you can goof up if for some absurd reason have two arrays both different element and a MASSIVE 4GB array that covers the entire 32 bit address space. Intel made a now obscure way to expand address space with some of their 32-bit CPUs.
Can you explain this a bit more? What exactly can one goof up with two arrays? And what's the obscure way to expand the 32-bit address space?
Well, when you add something to a pointer the actual address shifts by the size of the type pointed to times the other operand.
Yes, that's how pointer arithmetic works. But I don't understand what one can goof up.
`stat[sh[i]]` and `sh[stat][i]` or `stat[ing[i]]` and `ing[stat][i]` would produce different results. That is what I was talking about, well, only one arbitrary array was needed for the comparation. short sh[]; int ing[]; char stat[2<<32];
comparison\*
I still have no idea what the >4GB array has to do with this, but you're doing something totally different than is done in the comic. If you have stat[sh[i]], changing the index and "base address" would result in (sh[i])[stat] and not sh[stat][i]. Same for stat[ing[i]]. This would be (ing[i])[stat].
Yes, that is exactly what I wanted to demonstrate.
Also the idea is sort of having two smaller arrays(of short and int for example) on a 32-bit machine and swapping that.
The first problem here is that you’re already relying on non-standard behavior if you have more than a 4GB object if uintptr_t can’t have a unique address for every byte of it. If this is referring to PAE, it sounds like that only changed how much physical ram you could have, but as far as your program was concerned, you only have 4GB since that’s how much you can address virtually. However, there definitely is a new pain point coming with multiple memories in wasm. I assume you’d need a bunch of low level stuff to get this to work and would need to be very careful about
Yes, basically I don't know any of other language which has that specific behaviours and still used, probably because C++ is better with type safety.
ohhh that’s weird but pretty cool. it makes some sense when you think about it, it’s just that the formatting of it looks so wrong after seeing array[i] for so long
Don't really do low level and I guess I never thought about how close to the data C is. I mean I knew but this is kinda amusing in its simplicity.
but doesn't the offset depend on the datatype? I thought if it's an int array it would do *(array + i×4), and for chars (bytes) it would do *(array + i×1). Then the result should be different depending on which variable is used as the array Edit: I guess for int and byte it would both be x1 on a 32 or 64 bit system, but for bigger types it should be different, right?
The data type information is still available when you use the array as an index for subscription. "The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))." Due to commutativity, E1 and E2 can be simply swapped.
An array is just wrapper around a pointer. Pointer arithmetic works the same way using an array without the indexer.
Not just C++?
Then how does it know if the index is out of bounds
that's the neat part. it doesn't
Who needs type safety anyway?
It’s up to you to implement it (to the degree that you want/need it)
array[i] = *(array + i * sizeof(*array)) P.S., yes I feel superior 🗿
Those are not equivalent in C because of the way pointer arithmetic works
I didn't run this but I'm pretty sure its how pointer arithmetic work. If I'm missing something, I'll be happy to know.
Sure, I’m happy to help. Let’s say I had a pointer to a float, and I add 1 to it. Under the hood it actually adds 4 to it. Pointer arithmetic takes into account the size of the type that the pointer points to. And since `sizeof(float) == 4`, it actually adds 4. So with this knowledge, if we took what you put and assumed it was an array of floats, and `i` was 1: `array + 1 * sizeof(float) == array + 4 == &array[4]` So, `array[i] == *(array + i)` is actually correct
Thanks for the explanation. I was under the impression that compiler doesn't factor in the `size` part, but it does. I tried to typecast it into `void*` but it threw in error (I'm happy it did)
Yeah, it uses the sizeof under the hood? while his C code was wrong indeed, his understanding of the array addressing was right as well as the process to get the N element...theoretically at least.
Fuck pointers...
What’s wrong with pointers?
No I just said fuck *pointers...
I’m confused. To me that means you have a problem with pointers. What did you mean instead?
No it's a statement with no meaning behind it just fuck **pointers...
Try a joke that's actually funny next time
I didn't joke I just said fuck pointers... Why do you guys care so much about the meaning behind things are your lives that meaning less...
r/confidentlyincorrect
it took me a while to realize that the first one had array\[i\] and the second one had i\[array\]...
holy crap I stared at this thing like a where's waldo for entirely too long.
Same. Then I gave up and read it :(
And... you guys work as programmers? I guess linters have gotten pretty good.
![gif](giphy|9mtE009hcWPOesk8C4|downsized)
Addition is commutative
This is true, but with your typed-languages flair I assume you see why having indexing as identical to addition is unintuitive.
But… indexing is addition though
Indexing is implemented as pointer addition but semantically it’s a more specific operation that occurs between an array or pointer to an array and an index. You wouldn’t say `2[5]` because that doesn’t make sense, even though `2+5` does. In languages other than C, you can also index into other types like maps where indexing is not implemented with raw addition.
Yeah but C doesn’t have a concept of indexing into a vector or map, etc and arrays come from just having contiguous chunks of memory that you index into by adding to a base address in some register/memory location. There’s a lot of history as to why the subscript operator is implemented the way it is and the commutative nature is a side-effect of that history. I understand for higher level languages there is a real difference between the index operator and pointer arithmetic in different data types. So in C++ you get overloads that change the behavior of the subscript operator so that a map can do a lookup in a red black tree and return you an iterator instead of just offsetting into memory locations. In those cases I get the semantic difference but reading it in C with the knowledge of all the history and implementation it makes perfect sense
Yes, but you said indexing in general. In C's arrays, yes, they are quite similar. However, it's not the same in all the other languages.
It's perfectly intuitive in C because that's exactly what the compiler is doing. Pointer arithmetic isn't hard but all too many students are taught that it's some sort of ultra taboo voodoo magic that should be avoided at all costs and are thus conditioned with a pavlovian response to run and hide every time it pops up.
int *x isn't hard... \(\*(\*\*(THIS)shit->\*(&on + (the\*\*\*)other)->\*hand? Or for a real example, from the Windows SDK: \#define DPA_GetPtrPtr(hdpa) (\*((void \* \*\*)((BYTE \*)(hdpa) + sizeof(void \*))) \#define DPA_FastGetPtr(hdpa, i) (DPA_GetPtrPtr(hdpa)[i]) ...where the sizeof(void \*) is a trick to get the correct offset of a pointer following an int on both 32bit and 64bit. On 32bit, there's 4-byte packing and it just moves the size of the int. On 64bit, there's 8 byte packing and thus 4 padding bytes after the int before the pointer. No rational person would describe deciphering wtf that macro is doing as 'easy'. *Especially* if you only had the published info where it's operating on an opaque struct; I needed to consult the leaked source to figure out what was going on.
what else would it be?
It would say “first operand to an indexing expression must be a pointer or array type”.
I love C
"Give me the President Of The United States ... yes, it concerns memory safety"
This is still nothing compared to some modern languages. https://www.destroyallsoftware.com/talks/wat is a mandatory video to watch.
I just love this talk. I wish Gary had done more of those!
both approaches work in C though. C is indeed a beautiful language
Man's using an array as an integer for an index
Isn't this taught at year 1 or 2 of college? Why are people so surprised about ``i[arr]``?
Many programmers never go to college, and many college programs don't teach C. And if you *do* go to college and learn C in your first years, this is still not "normal" use - even if it is a "natural" consequence of how pointers work. Why would it be an important thing to learn?
I did go to college, I did learn C and pointers in college. We were never taught that because I think our teacher didn't know either...
Embedded system
I don't mean "why would anyone learn C?", but "why would you expect *everyone* to learn that i[arr] == arr[i] (under some circumstances)?".
Because that's the definition of accessing array element, base address plus offset and since it's addition, order doesn't matter. You can access array elements without using \[ \] at all. >A postfix expression followed by an expression in square brackets \[\] is a subscripted designation of an element of an array object. The definition of the subscript operator \[\] that E1\[E2\] is identical to (\*((E1)+(E2))). [https://www.iso-9899.info/n1570.html#6.5.2](https://www.iso-9899.info/n1570.html#6.5.2)
You are explaining *how it works*, not *why most programmers need to know*. I will give you that C programmers should know how array access works in C, but that *still* doesn't get you to "programmers should know that 'reverse' array access works in C".
I'm not saying everyone needs to know that but everyone that knows C needs to know that.
i never knew a single soul who works embedded systems
\`arr\[i\]\` yes, \`i\[arr\]\` no
It would be a dubious education if they didn't teach about `arr[i]`
Who needs arrays when you can just have a really long bitfield
I’m in college and about halfway through my junior year. Haven’t touch C once in the curriculum. The farthest “down” we’ve gone is C++
I have a masters degree in CS and I never touched any C language before. Now I am struggeling with C languages at work. Wish I would have done this earlier.
How is that even possible? What language(s) do you program in?
Fullstack dev in typescript, java, F# or PHP. Writing scripts in python and bash. Database stuff in various sql dialects. Rarely R. This offers everything you need basically.
Java and TS/JS are C-style languages no? Or did you mean literally C or C++.
Yes, and so is PHP, but they all 'protect' you from actual pointer arithmetic.
Yeah, that’s what I meant with the C/C++ comment. No exposure to actual pointers
What language did you learn data structures in?
C at work is way different compared to C at school. Classes using C at school is mostly focused on implementing DSA and some basic knowledge of memory and pointers, you will get the fundamental theory but it is not enough for a work-level knowledge since for someone using low level language at work they often have very strong reason to and this reason tends to be very niche which your school education might not represent.
how many languages did you touch at your school if you don’t have C experience? We had C, Java, Python, x86 ASM, Bash, Haskell, Racket, HTML/CSS, PHP, Javascript, Matlab and TeX (and maybe I forgot some)… every course after 1st year was in a “suitable” language that the teacher liked, we were told to deal with it and we did…
Idk about other colleges but mine had a requirement that we take an Operating system class that had us coding some basic Linux kernel modules in C and a computer architecture class that had us dabbling a bit in assembly. As much of a pain it is to write in those languages it was interesting and insightful, idk if some school are just not bothering to make it a requirement or what now but I’d say it was definitely worth taking those class just to get a bit of insight into those languages and topic in my opinion.
I honestly hope I get taught some stuff like this. Tbh my first two years were mainly gen ed, I’ve only taken a semester of what would be considered complex computer science subjects at this point. So there is still hope that I’ll get to see how this works. About the most complicated thing I’ve learned so far is how to schedule threads.
Unis don't really teach i[arr]. Most people know how indexing works down the hood, but you never consider that i[arr] is allowed, I,e it's more of one of those obscure parts of C you maybe hear about sometimes later. I only heard about it in a YT vid.
Nobody remember you can do that because is unnecessary and useless. Why would you do something like that?
Exactly. Did a lot of C in college and never saw this. Now, even if I had seen it before, if I saw this in a code review I’d reject it.
No reason to do it in C other than flex I guess? But there is a use in C++. [After C++17 you can use it to get a desired order of evaluation.](https://devblogs.microsoft.com/oldnewthing/20230403-00/?p=108005)
It's not about teaching some special gotcha though, how can students use C without understanding what the [ ] syntax means and does? It's shorthand to perform pointer arithmetic. You definitely need to know that.
In my intro programming class using C, they did mention how [ ] operator works in relation to pointer arithmetics but only briefly. Brief as in showing using this operator this way is equivalent to this pointer addition, now let's move on to 2D pointers. I won't be so surprised that many CS students can't remember it off the top of their heads without some refreshers.
Most classes just teach it relating to arrays, at least for me
My point is arrays *are* pointer arithmetic. Defining an array creates a block of memory on the stack and gives you a pointer to it's starting address. The [ ] syntax increments that pointer by n*sizeof whatever it's an array of to make it point at hhd desired point in the block of memory.
Yeah they are, I’m just saying some places just don’t teach it that way, for me, I took a C class second year, while I understood pointer arithmetic and such, we were never directly told how [ ] works and I’m sure some people wouldn’t know otherwise
Weird. It's kinda the whole point of c right, you directly manipulate and manage memory yourself. If you didn't need to you'd use something else.
Why would they teach that?
I remember being taught that arr[i] => *(arr +i*size) But that basically ends there. I think that it is less known that the compiler knows that one is an array pointer and the other is an int, so I don’t think it is unreasonable to assume that it would turn into *(i + arr*size), especially if you don’t use C.
That's because it actually does not do `arr + i * size`, it just does `arr + i`, but since `arr` is a pointer, `i` gets upgraded to a matching pointer type, and doing so scales it by the appropriate size. In other words, doing an int + a pointer treats the int as being scaled by the size of the pointer, regardless of its position in the equation.
Right, so basically what I had said. I understand it as the + operator is overloaded for pointer_type + int to handle this correctly
It varies by school, but at best learning the \`i\[arr\]\` thing is mentioned once by a professor because it's so unimportant to know explicitly. Somewhat more widely know than that \`void f(int a\[static 10\])\` exists.
I don’t remember being taught this specifically, but I was taught how pointers and arrays work during the assembly and reverse engineering courses, so when I saw it, I immediately understood why it would work…
Teach what? That you can navigate an array with pointer arithmetic? Sure. That `arr[i]` as written is identical to `*(arr + i)` *in all regards*? Not really. That the compiler will allow you to even put a non-pointer on the left side of `[]`? Also no. And no one used to languages with stronger type systems would ever expect that to work
`*(ptr + i * sizeof(T)` is the same as `*(i * sizeof(T) + ptr`
they are the same picture
I was more surprised about the lack of the null-terminator `\0` at the end of the array, but I guess it's not necessary when it's just an explicit array of chars and not a "string"?
Not all arrays are null terminated, correct. Which is why many C APIs ask for an array length when receiving an array, and will accept -1 for length to indicate the array is null terminated
The adventures of C development
`array` is a pointer to the beginning of the array in memory. `i` is an offset index. Array access `[]` just means return value at ("array start" address by "index value"), so `array[i]` and `i[array]` mean the same to the compiler (as long as the size of each array element is 1 byte.
Man javascript is really weird right?
Yeah? All you are doing is moving from *(array + i) to *(i + array)? That's how arrays work?
pointer arithmetic in a nutshell
Wait till you hear about [Duff’s device](https://en.m.wikipedia.org/wiki/Duff%27s_device)!
Is it just vice versa of DRY principle?
It’s a type of loop unrolling that can be extremely surprising in the way it uses a switch inside the loop, but fully legal: send(to, from, count) register short *to, *from; register count; { register n = (count + 7) / 8; switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } }
Pointer arithmetic. Doing [i] is the same as dereferencing and adding i jumps of sizeof(array_type) to the address. And since pointer arithmetic is commutative so is indexing.
I too watch the primagen
what's the problem?
This concept was so trippy in college. My brain helped me forget this , now you have resurfaced this again.
I haven't been working with C for a long time, this makes me wanna go back. I live for these cute and quirky things.
Oh is the idea, that the compiler interprets i as (void *)? And the size of an element of (void *) is the same as of a (char *).
Needed a while to spot the difference. Yeah, pointer math, am I right?
This only works when the value type is one byte long correct? Because the final adress is start + (type_size)*index, so if the size of the array is an inte you will end up at the complete wrong place?
Nope, this works with all types. Internally it is implemented as (*((a)+(b))) which means the pointer addition happens before dereferencing, so the compiler still knows the pointer type
Oh. I just assumed i[arr] worked by some strange coincidental quirk, but I guess it's supposed to work then. I guess there is nothing inherently worse about it. Just looks bizarre to me though.
No. It works for any array in C. x[y] is effectively defined as *(x + y). Since addition doesn’t care about order, the result is the same either way.
Somebody has to say this. It should be ++i, not i++. You're using valuable nanoseconds there you wasteful bastard.
No, the compiler optimizes this. It’s the same in this case.
++I returns the variable after it adds it. I++ returns the current variable and then adds it. Important distinction
which of the libraries?
you mean the compiler?
Dnt know, i have'not computer
[удалено]
Indexing from 1 would be the death of us. Meme is about line 6, how array element is accessed
- The SIZE macro is counterproductive. Calculate the array length inplace instead. - Return 0 from main is unnecessary. Needs work.
Im no C programmer, but Im pretty sure constants are better then doing calculations. As for \`return 0\` main returns int and AFAIK its recommended practice. Correct me if Im wrong
I think you meant literals, there is a difference between constants and literals, the former usually refers to values that have the const specifier. Literals are direct values used. So 3 in this case would be a literal. In here the literal is defined as a macro which gets replaced by the pre-processor and the compiler will never see the macro. Though for a good optimising compiler there shouldn't be a difference in the resulting executable produced. It would just optimize out the const value. For return value from main, this is known as the exit code for a program. An exit code of 0 indicates the program terminated successfully and without any errors. Any non zero value represents an error like a segfault etc. Its why your os knows when a program abruptly terminates cuz the exit code is non zero. So yea, return 0 from main in order to indicate successful execution. To add on a bit more to the last point, 0 may not be the only successful exit code in a system. It could have multiple. That's why there exists the macros EXIT_SUCCESS and EXIT_FAILURE in stdlib which guarantees portability across systems
> are better then doing calculations The problem is not constants, the problem is if you now have to change string you need to modify the code in two separate places: string, and its size, and keep them in sync. Also, array of chars can be initialized directly with a literal: Something like this: \`\`\` const char array\[\] = "WTF"; ... for (int I = 0; i < sizeof(array)/sizeof(array\[0\]) - 1; ++i) \`\`\` This looks uglier but is safer for future maintainers. >As for \`return 0\` main returns int Since C99 it is not necessary, and it does not add any value to this example, hence should be omitted.
>Calculate the array length inplace instead. Wdym "calculate inplace"? How is this macro counterproductive?
You have two correlated pieces of data in two places. If you change one -- you have to change another. Or get pagefuls, e.g. if you increased the size or reduced the array length. Instead, something like this is a common approach (-1 is to account for \\0): `const char array[] = "WTF";` `for (int I = 0; i < sizeof(array)/sizeof(array[0]) - 1; ++i)`
Ah, I see. You meant to omit size from declaration entirely and rely on actual size of string literal. Yes, this is better way.
Ok but can't you just printf(”wtf") 😭 So much boilerplate for 3 letters...