That is correct. From C99 §6.5.2.1/2:

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).

There's no magic. It's a 1-1 equivalence. As always when dereferencing a pointer (*), you need to be sure it's pointing to a valid address.

Answer from Matthew Flaschen on Stack Overflow
🌐
Quora
quora.com › Can-you-have-a-negative-index-in-an-array-in-C
Can you have a negative index in an array in C? - Quora
Answer (1 of 2): According to C, there is no explicit functionality specified for negative indices. For example, if you have an array of size 5 starting from index 0 and ending at index 4, if you try to access index ‘-1′, it would simplt ...
Top answer
1 of 7
27

The array indexing operation a[i] gains its meaning from the following features of C

  1. The syntax a[i] is equivalent to *(a + i). Thus it is valid to say 5[a] to get at the 5th element of a.

  2. Pointer-arithmetic says that given a pointer p and an integer i, p + i the pointer p advanced by i * sizeof(*p) bytes

  3. The name of an array a very quickly devolves to a pointer to the 0th element of a

In effect, array-indexing is a special case of pointer-indexing. Since a pointer can point to any place inside an array, any arbitrary expression that looks like p[-1] is not wrong by examination, and so compilers don't (can't) consider all such expressions as errors.

Your example a[-1] where a is actually the name of an array is actually invalid. IIRC, it is undefined if there's a meaningful pointer value as the result of the expression a - 1 where a is know to be a pointer to the 0th element of an array. So, a clever compiler could detect this and flag it as an error. Other compilers can still be compliant while allowing you to shoot yourself in the foot by giving you a pointer to a random stack slot.

The computer science answer is:

  • In C, the [] operator is defined on pointers, not arrays. In particular, it's defined in terms of pointer arithmetic and pointer dereference.

  • In C, a pointer is abstractly a tuple (start, length, offset) with the condition that 0 <= offset <= length. Pointer arithmetic is essentially lifted arithmetic on the offset, with the caveat that if the result of the operation violates the pointer condition, it is an undefined value. De-referencing a pointer adds an additional constraint that offset < length.

  • C has a notion of undefined behaviour which allows a compiler to concretely represent that tuple as a single number, and not have to detect any violations of the pointer condition. Any program that satisfies the abstract semantics will be safe with the concrete (lossy) semantics. Anything that violates the abstract semantics can be, without comment, accepted by the compiler and it can do anything it wants to do with it.

2 of 7
15

Arrays are simply laid out as contiguous chunks of memory. An array access such as a[i] is converted to an access to memory location addressOf(a)+i. This the code a[-1] is perfectly understandable, it simply refers to the address one before the start of the array.

This may seem crazy, but there are many reasons why this is allowed:

  • it is expensive to check whether the index i to a[-] is within bounds of the array.
  • some programming techniques actually exploit the fact that a[-1] is valid. For instance, if I know that a is not actually the start of the array, but a pointer into the middle of the array, then a[-1] simply gets the element of the array that is to the left of the pointer.
🌐
Medium
medium.com › hackernoon › negative-indexing-multi-arrays-in-c-b551ce252972
Negative Indexing Multi Arrays in C/++ | by Shirsh Zibbu | HackerNoon.com | Medium
April 26, 2019 - Cell Count: The number of cells ... C_i * S_i because: ... C/++ allows negative indexes in multi arrays since it is just a contiguous chunk of memory and index lookups is just pointer arithmetic on it....
🌐
HackerNoon
hackernoon.com › negative-indexing-multi-arrays-in-c-b551ce252972
Negative Indexing Multi Arrays in C/++ | HackerNoon
March 23, 2018 - The Redux Type Flow (revisited in 2018) Up Next → · Calculating browser support for the web · Shirsh Zibbu@zhirzh · Associate Software Engineer · Read my storiesAbout @zhirzh · programming#c-programming#arrays#tips-and-tricks · Arweave · ViewBlock ·
🌐
Sololearn
sololearn.com › en › Discuss › 1813052 › can-we-use-negative-index-in-c
Can we use negative index in c#. | Sololearn: Learn to code for FREE!
... Maninder $ingh Negative values are not permitted as index values by the C# compiler, which is more strict than C/C++ compilers which leave it to the programmer to not supply negative index values.
Find elsewhere
🌐
Cplusplus
cplusplus.com › forum › beginner › 220530
Are negative index in array allowed? - C++ Forum
Yes, that is why I said it had limited value in modern code (pretty much true for all array and pointer hacks). You can make it work with a static (fixed size) vector, I don't think that would be too hard. Worst case you could just overload a [] operator to fudge the index for you.
🌐
Davejingtian
davejingtian.org › 2017 › 08 › 03 › pitfalls-in-negative-indexing-in-c
Pitfalls in negative indexing in C | davejingtian.org
August 3, 2017 - Negative indexing in C, such as a[-1], is legit, although rarely used. There are reasons (pitfalls) why negative indexing is not recommended. This post discusses these pitfalls when using negative indexing (for fun).
🌐
Quora
quora.com › Why-are-negative-array-indices-accepted-in-C-language-Please-see-comments-for-details
Why are negative array indices accepted in C language? Please see comments for details. - Quora
Answer (1 of 2): An array in C language is basically a pointer. Sort of a constant pointer. This pointer gets translated from [code ]array[subscript] [/code]to [code ]*(array + subscript)[/code]. So, when you give a negative subscript, it doesn’t matter in terms of pointer arithmetic.
🌐
Ars OpenForum
arstechnica.com › forums › operating systems & software › programmer's symposium
Is using negative array indexing in c poor form? | Ars OpenForum
December 26, 2007 - </div></BLOCKQUOTE><BR><BR>It is ... not validate that the code is without error.<BR><BR>If you wish to use negative numbers for offsets you should not use arrays, you should use pointer arithmetic directly instead.<BR><BR>Wrap it up in a macro, if you wish....
🌐
Everything2
everything2.com › home › negative array indexing
Negative array indexing
January 1, 2003 - Questionable instance of C code, in which a negative number is used to index an array. For example: char st[5]; st[-1] = 0; This code looks...
🌐
CodeChef Discuss
discuss.codechef.com › general
negative index of array - general - CodeChef Discuss
August 22, 2013 - hi guys can you explain what the meaning of negative index. i read someone code and don’t understand because there like these: arr1[-1]='0'; so i search on net still i don’t find an answer. thx for answer.
🌐
Quora
quora.com › What-do-the-specs-tell-us-about-what-happens-if-someone-passes-a-negative-index-in-array-in-C-and-in-C++
What do the specs tell us about what happens if someone passes a negative index in array in C and in C++? - Quora
Answer (1 of 11): You would be reading or writing from a memory location that contains something else than members of your array. The array arithmetic in C is fairly straightforward: an array variable is merely a pointer to a memory location. It has no knowledge about the size of the array and ...
Top answer
1 of 2
6

Remember that when you index an array, you get the element at index N which is the element at &array[0] + sizeof(array[0]) * N.

Suppose you have this code:

#include <stdio.h>
int main()
{ 
    int a[5] = {5, 2, 7, 4, 3};
    int* b = &a[2];
    printf("%d", b[-1]); // prints 2
   
}
2 of 2
1

C arrays allow for negative indexing

Not really. Array indexing boils down to pointer arithmetic with the additive (+ and -) operators. From C17 6.5.6/8 emphasis mine:

If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. ...
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

The reason why - is allowed in the first place is because it can be useful for calculating a relative offset based on a certain index. But the offset still has to end up pointing at an item inside the array, or you invoke undefined behavior.

Your struct example actually does just that - there is no guarantee that those two arrays are allocated adjacently (even though it is very likely). The compiler is free to insert padding in between the two arrays. Also the compiler is free to assume that arr1 is never modified in case we do foo.arr2[-2]=3;.

This code could in theory print 2 2 2:

std::cout << foo.arr2[-2] << std::endl;
std::cout << foo.arr1[1] << std::endl; // store foo.arr1[1] in a register
foo.arr2[-2]=3;
std::cout << foo.arr1[1] << std::endl; // print once more using that same register

🌐
Reddit
reddit.com › r/programminglanguages › negative/signed integer indexing: yay or nay?
r/ProgrammingLanguages on Reddit: Negative/signed integer indexing: yay or nay?
September 13, 2021 -

Inko currently allows you to use negative/signed indexes for arrays and byte arrays. When used, indexing starts at the end. For example:

let numbers = Array.new(10, 20, 30)

numbers[-1] # => 30
numbers[-2] # => 20

I originally implemented this without much thought: Ruby did it, it was sometimes useful there, so I copied it.

Signed indexes bring some trouble though:

  1. You can only represent indexes up to (263)-1. Now I think most programs won't store that many values in a single collection, but it's something one has to keep in mind.

  2. You need to convert the signed indexes into unsigned indexes. This requires something like ((index % length) + length) % length, which is a fair number of instructions.

  3. I have doubts about how useful it really is.

Problem one and two come down to the same: complexity. For example, for Inko this comes in two pieces:

  1. A function that takes a signed index and an unsigned length, and converts the index to an unsigned index. My function does support collections larger that (263)-1 by upcasting the length and index to an i128, but the index size is still limited to (263)-1.

  2. A function to implement the modulo operator (not the remainder). Rust doesn't provide one (it uses remainder), so I had to write my own.

While this is hidden from the user, I do have to deal with it, and like most the less code I have to deal with the better.

Problem 3 is more subjective. The most commonly used negative index is simply -1 to get the last value. This however can be handled by introducing a dedicated last() function. I actually think this is more clear if one isn't used to signed indexes. Besides that, indexes like -2, -3, etc are rarely used in my experience. For example, in the GitLab Rails source code I can only find 37 instances of -1 being used, 11 instances of -2, and only one instance of -3.

With this in mind, I'm starting to think signed indexes aren't really worth the trouble. If you really need to index from the back, you can just do thing[thing.length - N].

What are the thoughts of the subreddit on this matter? Does your language support signed indexes? Or am I not too far off wanting to remove support for this?

Top answer
1 of 12
12
There's a better alternative that doesn't seem to be considered often. Let the language provide a type FromEnd that contains a single integer. Let the language provide a value END that contains the integer 0 (maybe use the token $ for this if your language likes that idea). Let the indexing operator be overloaded, such that it can take a value of type integer or a value of type FromEnd. That way, you can type numbers[END-1] == 30 (or numbers[$-1] == 30 perhaps). This avoids the need to repeat the array name, which I find gets very awkward. Also, you can support END-0, which is impossible without the overload.
2 of 12
12
You can only represent indexes up to 263-1. Now I think most programs won't store that many values in a single collection, but it's something one has to keep in mind. Really? If your machine only had ONE task, ONE collection, and that had elements occupying only ONE byte each, then you'd need 8 exabytes of storage for a regular non-sparse array to get to the limits of a 63-bit index. That's about the amount of RAM on a billion PCs. You need to convert the signed indexes into unsigned indexes. This requires something like ((index % length) + length) % length, which is a fair number of instructions. Why would you need to do that? Address calculations on typical PCs don't need any such conversions. If I write x := A[i], where A is a 1-based array of int, x and i are both signed ints, then the generated code might be just one instruction (R.x and R.i are registers): mov R.x, [R.i*8+A-8] What are the thoughts of the subreddit on this matter? Does your language support signed indexes? Or am I not too far off wanting to remove support for this? I have a default integer type which is 64 bits signed. It can be used for everything: storing positive and negative values; array indexing for all practical sizes; memory and object sizes; file and volume sizes; and file offsets, which can be negative. My arrays can anyway be N-based, which means the lower bound can be a negative value, example: [-16384 .. +16383]int counts This array can be indexed by all possible values of i16 (a signed 16-bit type).