Допускаются ли отрицательные индексы массива в 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), что , кажется , в порядке. Что вы думаете?

Ответы:


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.
Ратинавелу Мутхалиар,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.