Чи дозволені індекси негативного масиву в С?


115

Я просто читав якийсь код і виявив, що людина використовує arr[-2]для доступу до 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-бітовий інт-індекс, який негативний? Чи повинно індекс переходити на 64-бітний підписаний int перед розрахунком адреси?
Пол Р

4
@Paul, з §6.5.6 / 8 (Оператори аддитів), "Коли вираз, який має цілий тип, додається до або віднімається вказівник, результат має тип операнда вказівника. Якщо операнд покажчика вказує на елемент об'єкта масиву, і масив досить великий, результат вказує на зміщення елемента від вихідного елемента таким чином, що різниця підписів результуючого та вихідного елементів масиву дорівнює цілому виразу. " Тому я думаю, що це буде просуватися, і ((E1)+(E2))це буде (64-бітний) покажчик із очікуваним значенням.
Меттью Флашен

@Matthew: спасибі за це - здається, що це має працювати, як можна було б сподіватися.
Пол Р

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 STOP HELPING ICE

4
Знайдено у розділі 5.3 K&R, наприкінці кінця: 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
Це не що рідко - це дуже корисно, наприклад , в обробці зображень з операторами околиць.
Пол Р

Мені просто потрібно було це використовувати, оскільки я створюю пул пам’яті зі стеком та купою [структура / дизайн]. Стек, що росте у бік більш високих адрес пам'яті, купа зростає до нижчих адрес пам'яті. Зустріч посередині.
JMI MADISON

8

Що, мабуть, було те, що arrвказувало на середину масиву, отже, робило arr[-2]вказівку на щось у вихідному масиві, не виходячи за межі.


7

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

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


3
Це звучить як помилка компілятора.
tbleher

2

Я знаю, що на це питання відповіли, але я не втримався поділитися цим поясненням.

Я пам'ятаю Принципи дизайну компілятора, припустимо, що це 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. Маючи таблицю комбінаторних чисел, яка повідомляє вам гребінець [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
Хоча цей код може відповісти на питання, надаючи додатковий контекст стосовно того, чому та / або як цей код відповідає на питання, покращує його довгострокове значення.
β.εηοιτ.βε

Python groovy ... мають їх. Простий випадок використання - це доступ до останнього елемента масиву, не знаючи розміру масиву, що є дуже реальною вимогою у багатьох ситуаціях проекту. Також багато DSL виграють від цього.
Ратінавелу Муталіар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.