I don't think it's a real fundamental though - at least not for computing in general. It is true for C, and it is likely true for the many languages that implement things like C. But it's not inherently true that the 'fundamental' implementation of an array must exist as a C array does.
I don't know if it relies on C so much but what C is an abstraction over. An array in C is a set of data that exists in consecutive locations in memory. Memory addresses start at 0. Technically, a C array located at the start of addressable memory, by the nature of the way we've decided computers work in general, would start at 0. Plus as many extra zeros as your architecture can represent. This is generally the way computers are assumed to work.
Higher level general purpose programming languages exist primarily to serve as an abstraction over the fundamental architecture of computers. C is technically a higher level language than machine code and assembly code, both of which use the 'lowest address is zero' abstraction. Every other abstraction, including C exists over top of that.
My point is mostly, if you're trying to remain relatively close to abstracting over general computing keeping arrays and their index based on what they are fundamentally abstracting over makes sense.
In domain specific applications, it may make less sense.
I still find this view of programming languages to be too limited. It's still very C-focused, in the sense that it seems to assume every language is trying to do the same things C is in roughly the same ways C does it.
Yet this just isn't the limit of computers or programming languages. Consider Prolog or a Lisp. What meaningful abstraction do they provide to have a deep connection with a block of contiguous memory used to store multiple same-sized elements? I'm sure you'll find such things ultimately used if you go deep enough, but it's not a meaningful part of the abstraction the languages provide.
And if we can do away with the entire concept of a set of same-sized data stuffed into a contiguous block of memory, what need can there be to adhering to considering indexes as memory offsets?
The incredible amount of (possible) separation between programming languages and the exact way in which such programs are executed provides an extremely valuable and deep freedom in how programmers can think about and address problems and their solutions. I don't see how insisting that we minimize any possible differences between language abstractions and common hardware practice gains us anything.
Though it is a convention, it has demonstrable, objective benefits.
The use of 0 as the basis means that, in mathematical language, the indexing is homogeneous and that conversion between different units is a linear map.
Imagine if we have tape measure that measures both meters and centimeters. Imagine that the first tick on the tape measure, representing no displacement, is simultaneously labeled "1 cm" and "1 m" instead of 0.
Now, we no longer have a linear map to convert between cm and m. What we have is an affine map, like m = 1 + (cm - 1)/100.
An affine map is a strictly inferior alternative when we have the freedom to establish a linear map.
Homogeneous/linear is the superior default. One-based can be used in the special cases where it is nicer.
Here is one example: binary heaps. When we store a binary tree structure into a heap array, 1 based indexing makes the calculations nicer for navigating from parent to children or vice versa:
[1]
[2] [3]
[4][5] [6][7]
The children of every node [n] are [2n] and [2n + 1]. The parent of every node [n] is [n / 2] (floor-truncating division).
Under 0 based, it's not as nice:
[0]
[1] [2]
[3][4] [5][6]
The children are now [2n+1] and [2n+2]. One extra addition is required in the case of the left child. Finding the parent requires a subtraction: [(n-1)/2].
Another way to see the advantage of 1 based here is that every row of the tree starts with a power of : [1] [2] [4] ...
Because we are dealing with exponentiation, avoiding 0 helps: 0 is not a power of two, so to "boostrap" the exponentiation, we have to displace it.
Note that even though it is nice for a binary heap to use 1 based indexing, we still want to store that in an zero-based array, and just sacrifice the storage for the zero element.
If we use a zero based array, then the nice heap arithmetic we wrote in the source code will look nice in the object code, due to the map from the source code array to the object code array being a linear map, rather than an affine map.
It is almost always better to simulate a 1 based array by sacrificing a storage element, than to have the compiler uglify the beautiful indexing calculations for the sake of which we switched to 1 based in the first place.
In summary:
1. we should choose the representation which offers the most succinct indexing calculations for the given situation, and not for some emotional reasons like "children learn to count from 1 and non-programmers understand that best".
2. arrays should be zero-based to preserve the succinctness of the calculation through to the object code (linear map from source to object, not affine).