Gli indici di matrice negativi sono consentiti in C?


115

Stavo solo leggendo del codice e ho scoperto che la persona stava usando arr[-2]per accedere al 2 ° elemento prima del arr, in questo modo:

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

È permesso?

So che arr[x]è lo stesso di *(arr + x). Così arr[-2]è *(arr - 2), il che sembra OK. Cosa ne pensi?

Risposte:


168

È corretto. Da C99 §6.5.2.1 / 2:

La de fi nizione dell'operatore pedice [] è che E1 [E2] è identico a (* ((E1) + (E2))).

Non c'è magia. È un'equivalenza 1-1. Come sempre, quando si dereferenzia un puntatore (*), è necessario assicurarsi che punti a un indirizzo valido.


2
Nota anche che non devi dereferenziare il puntatore per ottenere UB. Il solo calcolo somearray-2è indefinito a meno che il risultato non sia compreso tra l'inizio di somearraye 1 oltre la sua fine.
RBerteig

34
Nei libri più vecchi si []faceva riferimento come uno zucchero di sintassi per l'aritmetica dei puntatori. Il modo preferito per confondere i principianti è scrivere 1[arr]- invece di arr[1]- e guardarli mentre indovina cosa dovrebbe significare.
Dummy00001

4
Cosa succede sui sistemi a 64 bit (LP64) quando si dispone di un indice int a 32 bit negativo? L'indice dovrebbe essere promosso a un int con segno a 64 bit prima del calcolo dell'indirizzo?
Paul R

4
@Paul, da §6.5.6 / 8 (Operatori additivi), "Quando un'espressione con un tipo intero viene aggiunta o sottratta da un puntatore, il risultato ha il tipo dell'operando del puntatore. Se l'operando del puntatore punta a un elemento di un oggetto array e l'array è abbastanza grande, il risultato punta a un offset di elemento dall'elemento originale in modo tale che la differenza tra i pedici degli elementi dell'array risultante e originale sia uguale all'espressione intera. " Quindi penso che sarà promosso e ((E1)+(E2))sarà un puntatore (64 bit) con il valore atteso.
Matthew Flaschen

@ Matteo: grazie per questo - sembra che dovrebbe funzionare come ci si potrebbe ragionevolmente aspettare.
Paul R,

63

Ciò è valido solo se arrè un puntatore che punta al secondo elemento in un array o a un elemento successivo. Altrimenti, non è valido, perché accederesti alla memoria al di fuori dei limiti dell'array. Quindi, ad esempio, questo sarebbe sbagliato:

int arr[10];

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

Ma questo andrebbe bene:

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

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

Tuttavia, è insolito utilizzare un pedice negativo.


Non arriverei al punto di dire che non è valido, solo potenzialmente disordinato
Matt Joiner

13
@ Matt: il codice nel primo esempio restituisce un comportamento non definito.
James McNellis

5
Non è valido. Secondo lo standard C, ha esplicitamente un comportamento indefinito. D'altra parte, se int arr[10];facesse parte di una struttura con altri elementi prima di essa, arr[-2]potrebbe essere potenzialmente ben definita, e potresti determinare se si basa su offsetof, ecc.
R .. GitHub STOP HELPING ICE

4
Trovato nella sezione 5.3 di K&R, verso la fine: 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.Tuttavia, il tuo esempio è migliore per aiutarmi a capirlo. Grazie!
Qiang Xu

4
Ci scusiamo per il filo della negromanzia, ma adoro come i K&R siano ambigui sul significato di "illegale". L'ultima frase fa sembrare che gli accessi fuori limite generino un errore di compilazione. Quel libro è veleno per i principianti.
Martin,

12

Mi suona bene. Tuttavia, sarebbe un caso raro che ne avresti legittimamente bisogno.


9
Non è così raro: è molto utile, ad esempio, nell'elaborazione di immagini con operatori di quartiere.
Paul R

Avevo solo bisogno di usarlo perché sto creando un pool di memoria con uno stack e un heap [struttura / design]. Lo stack cresce verso indirizzi di memoria superiori, l'heap cresce verso indirizzi di memoria inferiori. Incontro nel mezzo.
JMI MADISON

8

Quello che probabilmente arrpuntava al centro dell'array, quindi arr[-2]puntava a qualcosa nell'array originale senza uscire dai limiti.


7

Non sono sicuro di quanto sia affidabile, ma ho appena letto il seguente avvertimento sugli indici di array negativi su sistemi a 64 bit (presumibilmente LP64): http://www.devx.com/tips/Tip/41349

L'autore sembra dire che gli indici di array int a 32 bit con indirizzamento a 64 bit possono comportare calcoli di indirizzi errati a meno che l'indice dell'array non venga promosso esplicitamente a 64 bit (ad esempio tramite un cast di ptrdiff_t). Ho effettivamente visto un bug della sua natura con la versione PowerPC di gcc 4.1.0, ma non so se si tratta di un bug del compilatore (cioè dovrebbe funzionare secondo lo standard C99) o di un comportamento corretto (cioè l'indice ha bisogno di un cast su 64 bit per un comportamento corretto)?


3
Sembra un bug del compilatore.
tbleher

2

So che la domanda ha una risposta, ma non ho potuto resistere a condividere questa spiegazione.

Ricordo i principi di progettazione del compilatore, supponiamo che a sia un array int e la dimensione di int sia 2 e l'indirizzo di base per a sia 1000.

Come a[5]funzionerà ->

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

Questa spiegazione è anche il motivo per cui gli indici negativi negli array funzionano in C.

cioè se a[-5]accedo mi darà

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

Mi restituirà l'oggetto nella posizione 990. Con questa logica possiamo accedere agli indici negativi in ​​Array in C.


2

Sul perché qualcuno dovrebbe voler usare indici negativi, li ho usati in due contesti:

  1. Avere una tabella di numeri combinatori che ti dice che comb [1] [- 1] = 0; puoi sempre controllare gli indici prima di accedere alla tabella, ma in questo modo il codice appare più pulito ed è più veloce.

  2. Mettere un centinello all'inizio di un tavolo. Ad esempio, vuoi usare qualcosa di simile

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

ma poi dovresti anche controllare che isia positivo.
Soluzione: fai in modo che a[-1]sia -DBLE_MAXcosì, in modo che x&lt;a[-1]sia sempre falso.


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
Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo sul perché e / o come questo codice risponde alla domanda ne migliora il valore a lungo termine.
β.εηοιτ.βε

Python groovy ... li hai. Un semplice caso d'uso è quello di poter accedere all'ultimo elemento di un array senza conoscere la dimensione dell'array, un requisito molto reale in molte situazioni di progetto. Anche molti DSL ne traggono vantaggio.
Rathinavelu Muthaliar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.