The reference for std::array::operator[] states:
Returns a reference to the element at specified location pos. No bounds checking is performed.
I wrote this small program to check the behavior of operator[]
:
#include <array>
#include <cstddef>
#include <iostream>
using std::array;
using std::size_t;
using std::cout;
using std::endl;
#define MAX_SZ 5
int main (void){
array<int,MAX_SZ> a;
size_t idx = MAX_SZ - 1;
while(idx < MAX_SZ){
cout << idx << ":" << a[idx] << endl;
--idx;
}
cout << idx << ":" << a[idx] << endl;
return 0;
}
When compiled and run, the above program produces the following output:
4:-13104
3:0
2:-12816
1:1
0:-2144863424
18446744073709551615:0
Based on the above output, my question is:
Why doesn't above code give a segmentation fault error, when the value of idx
assumes the value 18446744073709551615
?
>> Why doesn't above code give a segmentation fault error, when the value of idx assumes the value 18446744073709551615 ?
Because this large number is 2**64 - 1, that is 2 raised to the 64th power, minus 1.
And as far as the array indexing logic is concerned, this is exactly the same thing as -1, because the 2**64 value is outside of what the 64 bits hardware can consider. So you are accessing (illegally) a[-1], and it happens to contain 0 in your machine.
In your memory, this is the word just before a[0]. It is memory in your stack, which you are perfectly allowed by the hardware to access, so no segmentation fault is expected to occur.
Your while loop uses a size_t index, which is essentially an unsigned 64 bits quantity. So when the index is decremented and goes from 0 to -1, -1 is interpreted by the loop control test as 18446744073709551615 (a bit pattern consisting of 64 bits all set to 1), which is way bigger than MAX_SZ = 5, so the test fails and the while loop stops there.
If you have the slightest doubt about that, you can check by controlling the memory values around array a[]. To do this, you can "sandwich" array a between 2 smaller arrays, say magica and magicb, which you properly initialize. Like this:
#include <array>
#include <cstddef>
#include <iostream>
using std::array;
using std::size_t;
using std::cout;
using std::endl;
#define MAX_SZ 5
int main (void){
array<int,2> magica;
array<int,MAX_SZ> a;
size_t idx = MAX_SZ - 1;
array<int,2> magicb;
magica[0] = 111222333;
magica[1] = 111222334;
magicb[0] = 111222335;
magicb[1] = 111222336;
cout << "magicb[1] : " << magicb[1] << endl;
while (idx < MAX_SZ) {
cout << idx << ":" << a[idx] << endl;
--idx;
}
cout << idx << ":" << a[idx] << endl;
return 0;
}
My machine is a x86-based one, so its stack grows towards numerically lower memory addresses. Array magicb is defined after array a in the source code order, so it is allocated last on the stack, so it has a numerically lower address than array a.
Hence, the memory layout is: magicb[0], magicb[1], a[0], ... , a[4], magica[0], magica[1]. So you expect the hardware to give you magicb[1] when you ask for a[-1].
This is indeed what happens:
magicb[1] : 111222336
4:607440832
3:0
2:4199469
1:0
0:2
18446744073709551615:111222336
As other people have pointed out, the C++ language rules do not define what you are expected to get from negative array indexes, and hence the people who wrote the compiler were at license to return whatever value suited them as a[-1]. Their sole concern was probably to write machine code that does not decrease the performance for well-behaved source code.