Допускаются ли отрицательные индексы массива в C?

115

Я просто читал код и обнаружил, что человек использовал arr[-2]для доступа ко второму элементу до arr, например:

|a|b|c|d|e|f|g|
       ^------------ arr[0]
         ^---------- arr[1]
   ^---------------- arr[-2]

Это разрешено?

Я знаю, что arr[x]это то же самое, что и *(arr + x). Так arr[-2]это *(arr - 2), что , кажется , в порядке. Что вы думаете?

bodacydo
источник

Ответы:

168

Это верно. Из C99 §6.5.2.1 / 2:

Определение оператора индекса [] заключается в том, что E1 [E2] идентично (* ((E1) + (E2))).

Никакой магии. Это эквивалент 1-1. Как всегда, при разыменовании указателя (*) вы должны быть уверены, что он указывает на действительный адрес.

Мэтью Флашен
источник
2
Также обратите внимание, что вам не нужно разыменовать указатель, чтобы получить UB. Просто вычисления somearray-2не определены, если результат не находится в диапазоне от начала somearrayдо 1 после его конца.
RBerteig
34
В старых книгах на []них ссылались как на синтаксический сахар для арифметики указателей. Любимый способ запутать новичков - писать 1[arr]вместо того, чтобы arr[1]смотреть, как они догадываются, что это означает.
Dummy00001
4
Что происходит в 64-битных системах (LP64), когда у вас есть 32-битный индекс int, который отрицателен? Должен ли индекс быть повышен до 64-битного подписанного int до вычисления адреса?
Paul R
4
@Paul, из §6.5.6 / 8 (Аддитивные операторы), «Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд указателя указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента относительно исходного элемента, так что разница индексов результирующего и исходного элементов массива равна целочисленному выражению. " Поэтому я думаю, что он будет продвигаться и ((E1)+(E2))будет (64-битным) указателем с ожидаемым значением.
Мэтью Флашен,
@Matthew: спасибо за это - похоже, что он должен работать, как и следовало ожидать.
Paul R
63

Это допустимо, только если arrуказатель указывает на второй элемент в массиве или более поздний элемент. В противном случае это недопустимо, потому что вы будете обращаться к памяти за пределами массива. Так, например, это было бы неправильно:

int arr[10];

int x = arr[-2]; // invalid; out of range

Но это было бы нормально:

int arr[10];
int* p = &arr[2];

int x = p[-2]; // valid:  accesses arr[0]

Однако использование отрицательного индекса является необычным.

Джеймс МакНеллис
источник
Я бы не стал говорить, что это недействительно, просто потенциально беспорядочно,
Мэтт Джойнер,
13
@Matt: код в первом примере дает неопределенное поведение.
Джеймс МакНеллис,
5
Это недействительно. По стандарту C он явно имеет неопределенное поведение. С другой стороны, если бы они int arr[10];были частью структуры с другими элементами до нее, arr[-2]потенциально могли бы быть четко определены, и вы могли бы определить, основана ли она на offsetofи т. Д.
R .. GitHub ОСТАНОВИТЬ ПОМОЩЬ ICE
4
Нашел в K&R Раздел 5.3, ближе к концу: If one is sure that the elements exist, it is also possible to index backwards in an array; p[-1], p[-2], and so on are syntactically legal, and refer to the elements that immediately precede p[0]. Of course, it is illegal to refer to objects that are not within the array bounds.Тем не менее, ваш пример лучше помогает мне понять его. Спасибо!
Цян Сюй,
4
Извините за некромантию, но мне просто нравится, что K&R неоднозначно относятся к тому, что означает «незаконный». Последнее предложение звучит так, как будто доступ за пределы допустимого диапазона вызывает ошибку компиляции. Эта книга - яд для новичков.
Мартин
12

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

Мэтт Джойнер
источник
9
Это не что редко - это очень полезно, например , в обработке изображений с операторами окрестностей.
Paul R
Мне просто нужно было это использовать, потому что я создаю пул памяти со стеком и кучей [структура / дизайн]. Стек растет в сторону более высоких адресов памяти, а куча - в сторону меньших адресов. Встреча посередине.
JMI MADISON
8

Вероятно, это arrуказывало на середину массива, следовательно, arr[-2]указывало на что-то в исходном массиве без выхода за пределы.

Игорь Зевака
источник
7

Я не уверен, насколько это надежно, но я только что прочитал следующее предостережение об отрицательных индексах массива в 64-битных системах (предположительно LP64): http://www.devx.com/tips/Tip/41349

Похоже, что автор говорит, что 32-битные индексы массива int с 64-битной адресацией могут привести к вычислению неверных адресов, если только индекс массива явно не повышен до 64-битного (например, через приведение ptrdiff_t). Я действительно видел ошибку его характера в версии gcc 4.1.0 для PowerPC, но я не знаю, является ли это ошибкой компилятора (т.е. должен работать в соответствии со стандартом C99) или правильным поведением (например, для индекса требуется приведение к 64 бит для правильного поведения)?

Пол Р
источник
3
Похоже на ошибку компилятора.
tbleher
2

Я знаю, что на вопрос дан ответ, но я не мог удержаться от этого объяснения.

Я помню принципы построения компилятора. Предположим, что a - массив int, размер int равен 2, а базовый адрес a равен 1000.

Как a[5]будет работать ->

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

Это объяснение также является причиной того, почему отрицательные индексы в массивах работают в C.

т.е. если я получу доступ, a[-5]он даст мне

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

Он вернет мне объект в местоположении 990. С помощью этой логики мы можем получить доступ к отрицательным индексам в массиве в C.

Аджинкья Патил
источник
2

Что касается того, зачем кому-то использовать отрицательные индексы, я использовал их в двух контекстах:

  1. Имея таблицу комбинаторных чисел, в которой говорится, что comb [1] [- 1] = 0; вы всегда можете проверить индексы перед доступом к таблице, но так код выглядит чище и выполняется быстрее.

  2. Ставим сантинель в начало таблицы. Например, вы хотите использовать что-то вроде

     while (x < a[i]) i--;

но тогда вы также должны проверить это iположительно.
Решение: сделать так , чтобы a[-1]это -DBLE_MAX, так что x&lt;a[-1]всегда будет ложным.

Сантьяго Эджидо Артеага
источник
0
#include <stdio.h>

int main() // negative index
{ 
    int i = 1, a[5] = {10, 20, 30, 40, 50};
    int* mid = &a[5]; //legal;address,not element there
    for(; i < 6; ++i)
    printf(" mid[ %d ] = %d;", -i, mid[-i]);
}
Ратинавелу Мутхалиар
источник
1
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, улучшает его долгосрочную ценность.
β.εηοιτ.βε
Питон классный ... есть они. Простой вариант использования - можно получить доступ к последнему элементу массива, не зная его размера, что является очень реальным требованием во многих ситуациях Project. Также от этого выигрывают многие DSL.
Ратинавелу Мутхалиар,