Почему отрицательные индексы массива имеют смысл?

14

Я наткнулся на странный опыт программирования на Си. Рассмотрим этот код:

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

Когда я компилирую и запускаю это, я не получаю никаких ошибок или предупреждений. Как сказал мой лектор, индекс массива -1обращается к другой переменной. Я все еще в замешательстве, с какой стати язык программирования имеет такую ​​возможность? Я имею в виду, зачем разрешать отрицательные индексы массива?

Мохаммед Фавзан
источник
2
Хотя этот вопрос мотивирован тем, что C является конкретным языком программирования, я думаю, что его можно понять как концептуальный вопрос, который здесь является онтопическим (если не совсем).
Рафаэль
7
@ Рафаэль Я не согласен и считаю, что он должен принадлежать SO, так или иначе, это поведение учебника не определено (ссылка на память вне массива), и соответствующие флаги компилятора должны предупреждать об этом
ratchet freak
Я согласен с @ratchetfreak. Кажется, это недостаток компилятора, поскольку допустимый диапазон индекса равен [0, 5]. Все, что находится снаружи, должно быть ошибкой компиляции / времени выполнения. Как правило, векторы являются частным случаем функций, чей индекс первого элемента зависит от пользователя. Поскольку контракт C состоит в том, что элементы начинаются с индекса 0, доступ к отрицательным элементам является ошибкой.
Val
2
@Raphael C имеет две особенности по сравнению с типичными языками с массивами, которые здесь имеют значение. Одна из них состоит в том, что в C есть подмассивы, и обращение к элементу -1подмассива является вполне допустимым способом ссылки на элемент перед этим массивом в большем массиве. Другая причина в том, что если индекс недопустим, программа недопустима, но в большинстве реализаций вы получите тихое плохое поведение, а не ошибку вне допустимого диапазона.
Жиль "ТАК - перестань быть злым"
4
@Gilles Если в этом суть вопроса, это действительно должно было быть при переполнении стека .
Рафаэль

Ответы:

27

Операция индексации массива a[i]приобретает смысл из следующих особенностей языка C

  1. Синтаксис a[i]эквивалентен *(a + i). Таким образом, справедливо сказать, 5[a]чтобы получить 5-й элемент a.

  2. Указатель арифметику говорит , что данный указатель pи целое i, p + i указатель pвыдвинутых i * sizeof(*p)байт

  3. Имя массива aочень быстро превращается в указатель на 0-й элементa

По сути, индексирование массивов является частным случаем индексирования указателей. Так как указатель может указывать на любое место внутри массива, любое произвольное выражение, которое выглядит, неp[-1] является неправильным при проверке, и поэтому компиляторы не (не могут) рассматривать все такие выражения как ошибки.

Ваш пример, a[-1]где aна самом деле имя массива фактически неверно. IIRC, оно не определено, если в результате выражения, a - 1где aизвестно, что это указатель на 0-й элемент массива , имеется значимое значение указателя . Таким образом, умный компилятор может обнаружить это и пометить как ошибку. Другие компиляторы могут быть совместимы, позволяя вам стрелять себе в ногу, указывая указатель на случайный слот стека.

Ответ по информатике:

  • В Си []оператор определяется по указателям, а не по массивам. В частности, это определяется с точки зрения арифметики указателя и разыменования указателя.

  • В C указатель является абстрактным кортежем (start, length, offset)с условием, что 0 <= offset <= length. Арифметика указателя - это, по существу, арифметика отмены смещения, с оговоркой, что если результат операции нарушает условие указателя, это неопределенное значение. Отмена ссылки на указатель добавляет дополнительное ограничение offset < length.

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

Хари
источник
Пожалуйста, попробуйте дать общий ответ, а не тот, который зависит от особенностей конкретного языка программирования.
Рафаэль
6
@ Рафаэль, вопрос был явно о C. Я думаю, что я обратился к конкретному вопросу о том, почему компилятору C разрешено компилировать, казалось бы, бессмысленное выражение в определении C.
Хари
Вопросы о C в частности здесь оффтопны; обратите внимание на мой комментарий по этому вопросу.
Рафаэль
5
Я считаю, что сравнительный лингвистический аспект вопроса все еще полезен. Я полагаю, что я дал довольно «компьютерное» тематическое описание того, почему конкретная реализация показала конкретную конкретную семантику.
Хари
15

Массивы просто выкладываются как непрерывные куски памяти. Доступ к массиву, такой как [i], преобразуется в доступ к адресу ячейки памяти по адресу (a) + i. Этот код a[-1]вполне понятен, он просто ссылается на адрес перед началом массива.

Это может показаться сумасшедшим, но есть много причин, почему это разрешено:

  • дорого проверить, находится ли индекс i для [-] в пределах массива.
  • некоторые методы программирования на самом деле используют тот факт, что a[-1]является действительным. Например, если я знаю, что aэто на самом деле не начало массива, а указатель на середину массива, то a[-1]просто получает элемент массива, который находится слева от указателя.
Дэйв Кларк
источник
6
Другими словами, это, вероятно, не должно использоваться. Период. Что, вас зовут Дональд Кнут, и вы пытаетесь сохранить еще 17 инструкций? Конечно, идти вперед.
Рафаэль
Спасибо за ответ, но я не понял идею. Кстати, я буду читать это снова и снова, пока не пойму .. :)
Мухаммед Фавзан,
2
@Raphael: Реализация объектной модели колы использует позицию -1 для хранения виртуальной таблицы : piumarta.com/software/cola/objmodel2.pdf . Таким образом, поля хранятся в положительной части объекта, а таблица - в отрицательной. Я не могу вспомнить детали, но я думаю, что это связано с последовательностью.
Дейв Кларк,
@ DeZéroToxin: Массив - это просто место в памяти, рядом с которым расположены логические части массива. Но на самом деле массив - это просто указатель.
Дэйв Кларк,
1
@Raphael, a[-1]имеет смысл в некоторых случаях a, в данном конкретном случае это совершенно нелегально (но не
перехвачено
4

Как объясняют другие ответы, это неопределенное поведение в Си. Предположим, что Си был определен (и в основном используется) как «ассемблер высокого уровня». Пользователи C ценят его за его бескомпромиссную скорость, и проверка материала во время выполнения (в основном) исключается ради чистой производительности. Некоторые конструкции C, которые выглядят бессмысленными для людей, пришедших с других языков, имеют идеальный смысл в C, как это a[-1]. Да, это не всегда имеет смысл (

vonbrand
источник
1
Мне нравится этот ответ. Дает реальную причину, почему это нормально.
darxsys
3

Можно использовать такую ​​функцию для написания методов выделения памяти, которые обращаются к памяти напрямую. Одним из таких применений является проверка предыдущего блока памяти с использованием отрицательного индекса массива, чтобы определить, можно ли объединить два блока. Я использовал эту функцию при разработке диспетчера энергонезависимой памяти.

Терон W Genaux
источник
2

C не является строго типизированным. Стандартный компилятор C не будет проверять границы массива. Другое дело, что массив в C - это не что иное, как непрерывный блок памяти, и индексирование начинается с 0, поэтому индекс -1 - это местоположение любого битового шаблона до a[0].

Другие языки хорошо используют отрицательные индексы. В Python a[-1]вернет последний элемент, a[-2]вернет второй к последнему элемент и так далее.

saadtaame
источник
2
Как соотносятся строгая типизация и индексы массивов? Существуют ли языки с типом для натуралов, где индексы массивов должны быть натуральными?
Рафаэль
@Raphael Насколько я знаю, строгая типизация означает, что ошибки типа обнаруживаются. Массив является типом, IndexOutOfBounds является ошибкой, поэтому в строго типизированном языке об этом будет сообщено, в C этого не будет. Это то, что я имел в виду.
saadtaame
На языках, которые я знаю, индексы массивов относятся к типу int, a[-5]и, в общем, int i; ... a[i] = ...;они правильно напечатаны. Ошибки индекса обнаруживаются только во время выполнения. Конечно, умный компилятор может обнаружить некоторые нарушения.
Рафаэль
@ Рафаэль Я говорю о типе данных массива в целом, а не о типах индекса. Это объясняет, почему C позволяет пользователям писать [-5]. Да, -5 - это правильный тип индекса, но он выходит за границы, и это ошибка. В моем ответе нет упоминания о проверке типов во время компиляции или во время выполнения.
saadtaame
1

Простыми словами:

Все переменные (включая массивы) в C хранятся в памяти. Допустим, у вас есть 14 байтов «памяти», и вы инициализируете следующее:

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

Также рассмотрим размер int как 2 байта. Затем, гипотетически, в первых 2 байтах памяти будет сохранено целое число a. В следующих 2 байтах будет сохранено целое число первой позиции массива (что означает массив [0]).

Затем, когда вы говорите, что массив [-1] подобен обращению к целому числу, сохраненному в памяти, которое находится непосредственно перед массивом [0], которое в нашем гипотетически является целым числом a. На самом деле, это не совсем то, как переменные хранятся в памяти.

Dchris
источник
0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;
JMI MADISON
источник
Добро пожаловать в CS.SE! Мы ищем ответы, которые приходят с объяснением или описанием чтения. Мы не сайт кодирования, и мы не хотим, чтобы ответы были просто блоком кода. Вы можете подумать, можете ли вы отредактировать свой ответ, чтобы предоставить такую ​​информацию. Спасибо!
DW