В чем разница в том, как C и Java обрабатывают доступ к границам массивов?

В Java мы получаем прекрасное исключение при попытке доступа и массива вне границ, но в C это не так:

#include  int main() { int x[10] = {1,2,3}; // After the third index, I know that the rest are 0. int i = 0; while (i<99) { // Here I exceed the size of the array, printf("%d",x[i]); // printing non-existent indexes. i++; } return 0; } 

И выход:

 12300000001021473443840268687241986531780216078041842686812-12686816199941806157 0338438-2199933010019993299657804184019993359001214734438441990611-819265930944- 8192925345321-122881852697619690479012147344384268694020027537742147344384145346 587600214734438400102686884-819226869482003047601567625672026869562002753732-120 02706633004199040214734438402020893505321130682200320201752380100000243875924666 4687366080-21479789413447414207980 Process returned 1 (0x1) execution time : 0.719 s Press any key to continue. 

Технически, что именно произошло? Это не совсем «переполнение int size», верно?

В C Доступ к внешним границам массива – неопределенное поведение, и результат непредсказуем, он может работать корректно, может быть seg-fault и т. Д. Проект стандарта C99 в Annex J.2 Неопределенное поведение и перечисляет следующие пуля:

Индекс массива выходит за допустимые пределы, даже если объект, по-видимому, доступен с заданным индексом (как в выражении lvalue a [1] [7], указанном в заявлении int a [4] [5]) (6.5.6).

раздел 6.5.6 пункт 8 , который является нормативным, дает подробности.

С другой стороны, раздел 10.4 языка Java Language Specification позволяет исключить исключение из массива вне пределов:

Все обращения к массиву проверяются во время выполнения; попытка использовать индекс, который меньше нуля или больше или равен длине массива, вызывает выброс ArrayIndexOutOfBoundsException .

Java и C имеют разные концепции дизайна. Java выходит из своего пути, чтобы избежать неопределенного поведения, в то время как C и C ++ предоставляют широту реализации. Несколько хороших статей по этому поводу:

  • Почему языковые дизайнеры не допускают неопределенного поведения
  • Философия за неопределенным поведением
  • Неопределенное поведение в Java

Просто добавлю, что сказал Шафик :

Технически, что именно произошло? Это не совсем «переполнение int size», верно?

Массив занимает 10 * sizeof(int) байтов памяти. Когда вы печатаете значения массива с индексом выше границы этого размера, вы фактически получаете доступ к частям памяти, которые не были выделены для массива. Это может быть что угодно. Это могут быть значения другой переменной; это могут быть инструкции; это может быть защищенная память, к которой у вас нет доступа к бизнесу.

Это не переполнение int size, а переполнение в доступе к памяти. Когда вы объявляете x[10] в памяти, он будет распределять адрес и хранить 0 в 10 раз:

 x[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 

Когда вы заявляете:

 x[10] = {1, 2, 3} 

он присваивается как {1, 2, 3, 0, 0, 0, 0 , 0, 0 , 0} Но вы печатаете за пределами выделенной памяти, поэтому может быть что-либо сохраненное, которое не находится в руке вашей программы.

Я запускал вашу программу, а также печатал адреса и значения x[i]s и я мог видеть адреса и значения, такие как:

 x[0] = 0x7fff4b1b6290: 1 x[1] = 0x7fff4b1b6294: 2 x[2] = 0x7fff4b1b6298: 3 x[3] = 0x7fff4b1b629c: 0 x[4] = 0x7fff4b1b629c: 0 x[5] = 0x7fff4b1b62a0: 0 x[6] = 0x7fff4b1b62a4: 0 x[7] = 0x7fff4b1b62a8: 0 x[8] = 0x7fff4b1b62ac: 0 x[9] = 0x7fff4b1b62b0: 0 x[10] = 0x7fff4b1b62b4: 0 x[11] = 0x7fff4b1b62b8: 11 x[12] = 0x7fff4b1b62bc: 293 x[13] = 0x7fff4b1b62c0: 0 x[14] = 0x7fff4b1b62c4: 0 x[15] = 0x7fff4b1b62c8: 212348189 

Таким образом, вы можете видеть, пока x[9] вы получаете правильный вывод, но после этого он просто напечатает значение на следующем адресе и будет случайным.

В компьютерном программировании проверка границ – это любой метод определения, находится ли переменная в пределах каких-либо границ до ее использования. Он обычно используется для обеспечения того, чтобы число соответствовало заданному типу (проверка диапазона) или что переменная, используемая в качестве индекса массива, находится в границах массива (проверка индекса). Проверка с ошибками обычно приводит к генерации какого-либо сигнала исключения.

См. http://lelanthran.com/deranged/?p=182

Люди, проектирующие C, хотели, чтобы язык был настолько быстрым и гибким, насколько это возможно, поэтому время выполнения делает то, что сказал программист, даже если он разрушительный. В Java разработчики защищают программиста от чего-то глупого, поэтому каждый раз, когда вы обращаетесь к массиву, он сначала проверяет, находится ли доступ в пределах границ.

Один из подходов не обязательно лучше другого, но каждый подход лучше при выполнении разных действий.