Как уже объяснили другие, запись за пределы вашего массива - UB.
Я хотел бы указать на кое-что еще, что вы можете не осознавать:
Из вашего вопроса видно, что вы ожидаете, что доступ к arr [3] перезапишет вашу переменную i .
Даже если компилятор выделит ваши локальные переменные в порядке их появления в вашем исходном коде (что вовсе не гарантировано!), Вы НЕ перезапишете i .
Причина этого в том, что обычно стек растет вниз. Поэтому, если компилятор будет следовать вашей последовательности размещений в стеке, i получит самый низкий адрес в вашем коде.
Но чтобы разрешить нормальную арифметику указателей (что происходит при использовании записи массива []), увеличение индексов в arr пойдет на вверх в стеке, и вы перезапишете запись активации вашей функции , Очень плохо.
Попробуйте эту версию своего кода (скомпилируйте с -fstack-protector-all в GCC):
#include <stdio.h>
void smash(void){
int arr[3] = {50, 7, 30};
int i = 2;
arr[3] = 90;
//Let's look at the addresses
printf("&i = %p, &arr[0] = %p, , &arr[3] = %p\n", &i, &arr[0], &arr[3]);
printf("arr[2] = %d, arr[3] = %d, i = %d\n", arr[2], arr[3], i);
int i2 = 3;
//Small loop limit can trigger stack protector *upon* return from smash()
//Large loop limit will create a segfault *before* returning from smash()
for(; i2 < 10; i2++)
arr[i2] = 99999;
//Just to see where we crash.
printf("%d\n", i2);
}
int main(void)
{
smash();
return 0;
}