Perché gli indici di array negativi hanno senso?


14

Mi sono imbattuta in una strana esperienza nella programmazione C. Considera questo codice:

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;
}

Quando compilo ed eseguo questo, non ricevo alcun errore o avviso. Come ha detto il mio docente, l'indice dell'array -1accede a un'altra variabile. Sono ancora confuso, perché mai un linguaggio di programmazione ha questa capacità? Voglio dire, perché consentire indici di array negativi?


2
Mentre questa domanda è motivata con C come linguaggio di programmazione concreto, penso che possa essere intesa come una domanda concettuale che è ontopica qui (se a malapena).
Raffaello

7
@Raphael Non sono d'accordo e credo che dovrebbe appartenere a SO, in entrambi i casi si tratta di un comportamento indefinito del libro di testo (che fa riferimento alla memoria all'esterno dell'array) e che i flag del compilatore appropriati dovrebbero avvertire di ciò
maniaco del cricchetto

Sono d'accordo con @ratchetfreak. Sembra essere un difetto del compilatore poiché l'intervallo di indice valido è [0, 5]. Tutto ciò che è all'esterno deve essere un errore di compilazione / runtime. In generale, i vettori sono casi particolari di funzioni il cui primo indice di indice dipende dall'utente. Poiché il contratto C indica che gli elementi iniziano con l'indice 0, è errore accedere agli elementi negativi.
Val

2
@Raphael C ha due peculiarità rispetto alle lingue tipiche con array che contano qui. Uno è che C ha subarray e fare riferimento all'elemento -1di un subarray è un modo perfettamente valido per fare riferimento all'elemento prima di quell'array nell'array più grande. L'altro è che se l'indice non è valido, il programma non è valido, ma nella maggior parte delle implementazioni otterrai un comportamento silenzioso, non un errore fuori portata.
Gilles 'SO- smetti di essere malvagio' il

4
@Gilles Se questo è il punto della domanda, questo avrebbe dovuto essere effettivamente su Stack Overflow .
Raffaello

Risposte:


27

L'operazione di indicizzazione dell'array a[i]ottiene il suo significato dalle seguenti caratteristiche di C

  1. La sintassi a[i]è equivalente a *(a + i). Quindi è valido dire 5[a]di arrivare al 5 ° elemento di a.

  2. Il puntatore-aritmetico afferma che, dato un puntatore pe un numero intero i, p + i il puntatore è pavanzato di i * sizeof(*p)byte

  3. Il nome di un array asi trasforma rapidamente in un puntatore al 0 ° elemento dia

In effetti, l'indicizzazione di array è un caso speciale di indicizzazione di puntatori. Dato che un puntatore può puntare verso qualsiasi posto all'interno di un array, qualsiasi espressione arbitraria che sembra nonp[-1] è sbagliata dall'esame, e quindi i compilatori non (non possono) considerare tutte queste espressioni come errori.

Il tuo esempio in a[-1]cui aè effettivamente il nome di un array non è effettivamente valido. IIRC, non è definito se esiste un valore di puntatore significativo come risultato dell'espressione in a - 1cui asi sa che è un puntatore al 0 ° elemento di un array. Quindi, un compilatore intelligente potrebbe rilevare questo e contrassegnarlo come un errore. Altri compilatori possono comunque essere conformi mentre ti permettono di spararti nel piede dandoti un puntatore a uno slot casuale.

La risposta di informatica è:

  • In C, l' []operatore è definito su puntatori, non su array. In particolare, è definito in termini di aritmetica del puntatore e di dereference del puntatore.

  • In C, un puntatore è astrattamente una tupla (start, length, offset)con la condizione che 0 <= offset <= length. L'aritmetica del puntatore viene essenzialmente sollevata dall'aritmetica sull'offset, con l'avvertenza che se il risultato dell'operazione viola la condizione del puntatore, si tratta di un valore indefinito. La de-referenziazione di un puntatore aggiunge un ulteriore vincolo offset < length.

  • C ha una nozione di ciò undefined behaviourche consente a un compilatore di rappresentare concretamente quella tupla come un singolo numero e di non dover rilevare alcuna violazione della condizione del puntatore. Qualsiasi programma che soddisfi la semantica astratta sarà al sicuro con la semantica concreta (con perdita). Qualsiasi cosa che violi la semantica astratta può essere, senza commenti, accettata dal compilatore e può fare tutto ciò che vuole farci.


Prova a dare una risposta generale, non una a seconda delle idiosincrasie di un particolare linguaggio di programmazione.
Raffaello

6
@Raphael, la domanda era esplicitamente su C. Penso di aver affrontato la domanda specifica del perché un compilatore C è autorizzato a compilare un'espressione apparentemente priva di significato all'interno della definizione di C.
Hari

Le domande su C in particolare sono fuori tema qui; nota il mio commento sulla domanda.
Raffaello

5
Credo che l'aspetto linguistico comparativo della domanda sia ancora utile. Credo di aver dato una descrizione abbastanza "informatica" del perché un'implementazione specifica mostrasse una specifica semantica concreta.
Hari,

15

Le matrici sono semplicemente disposte come blocchi contigui di memoria. Un accesso ad array come [i] viene convertito in un accesso all'indirizzo di posizione della memoria Of (a) + i. Questo codice a[-1]è perfettamente comprensibile, si riferisce semplicemente all'indirizzo uno prima dell'inizio dell'array.

Questo può sembrare pazzo, ma ci sono molte ragioni per cui questo è permesso:

  • è costoso verificare se l'indice da un [-] rientra nei limiti dell'array.
  • alcune tecniche di programmazione sfruttano effettivamente il fatto che a[-1]è valido. Ad esempio, se so che in arealtà non è l'inizio dell'array, ma un puntatore al centro dell'array, quindi a[-1]ottiene semplicemente l'elemento dell'array che si trova a sinistra del puntatore.

6
In altre parole, probabilmente non dovrebbe essere usato. Periodo. Come, ti chiami Donald Knuth e cerchi di salvare altre 17 istruzioni? Certamente, vai avanti.
Raffaello

Grazie per la risposta, ma non ho avuto l'idea. A proposito, lo rileggerò più volte fino a quando non
capirò

2
@Raphael: L'implementazione del modello a oggetti di cola utilizza la posizione -1 per memorizzare la vtable: piumarta.com/software/cola/objmodel2.pdf . Pertanto i campi sono memorizzati nella parte positiva dell'oggetto e la vtable in negativo. Non riesco a ricordare i dettagli, ma penso che abbia a che fare con coerenza.
Dave Clarke,

@ DeZéroToxin: un array è in realtà solo una posizione nella memoria, con alcune posizioni accanto che fanno logicamente parte dell'array. Ma davvero, un array è solo un puntatore.
Dave Clarke,

1
@Raphael, a[-1]ha perfettamente senso per alcuni casi di a, in questo caso particolare è chiaramente illegale (ma non catturato dal compilatore)
vonbrand

4

Come spiegano le altre risposte, questo è un comportamento indefinito in C. Considera che C è stato definito (ed è principalmente usato) come un "assemblatore di alto livello". Gli utenti di C lo apprezzano per la sua velocità senza compromessi e il controllo delle cose in fase di esecuzione è (principalmente) fuori discussione per motivi di pura performance. Alcuni costrutti C che sembrano insensati per le persone che provengono da altre lingue hanno perfettamente senso in C, come questo a[-1]. Sì, non ha sempre senso (


1
Mi piace questa risposta. Fornisce una vera ragione per cui questo va bene.
darxsys,

3

Si può usare una tale funzione per scrivere metodi di allocazione della memoria che accedono direttamente alla memoria. Uno di questi usi è controllare il blocco di memoria precedente usando un indice di array negativo per determinare se i due blocchi possono essere uniti. Ho usato questa funzione quando sviluppo un gestore di memoria non volatile.


2

C non è fortemente tipizzato. Un compilatore C standard non controlla i limiti dell'array. L'altra cosa è che un array in C non è altro che un blocco contiguo di memoria e l'indicizzazione inizia da 0, quindi un indice di -1 è la posizione di qualunque bit-pattern sia precedente a[0].

Altre lingue sfruttano gli indici negativi in ​​modo piacevole. In Python, a[-1]restituirà l'ultimo elemento, a[-2]restituirà il penultimo elemento e così via.


2
Come si collegano la tipizzazione forte e gli indici di array? Esistono lingue con un tipo per i naturali in cui gli indici di array devono essere naturali?
Raffaello

@Raphael Per quanto ne so, la digitazione forte significa che vengono colti errori di tipo. Un array è un tipo, IndexOutOfBounds è un errore, quindi in un linguaggio fortemente tipizzato questo verrà riportato, in C no. Ecco cosa intendevo.
Saadtaame,

Nelle lingue che conosco, gli indici di array sono di tipo int, quindi a[-5]e, più in generale, int i; ... a[i] = ...;sono tipizzati correttamente. Gli errori dell'indice vengono rilevati solo in fase di esecuzione. Naturalmente, un compilatore intelligente può rilevare alcune violazioni.
Raffaello

@Raphael Sto parlando del tipo di dati array nel suo insieme, non dei tipi di indice. Questo spiega perché C consente agli utenti di scrivere un [-5]. Sì, -5 è il tipo di indice corretto ma è fuori limite e questo è un errore. Non vi è alcuna menzione del controllo del tipo di compilazione o di runtime nella mia risposta.
Saadtaame,

1

In parole semplici:

Tutte le variabili (comprese le matrici) in C sono memorizzate. Supponiamo che tu abbia 14 byte di "memoria" e inizializzi quanto segue:

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

Inoltre, considera la dimensione di un int come 2 byte. Quindi, ipoteticamente, nei primi 2 byte di memoria verrà salvato l'intero a. Nei successivi 2 byte verrà salvato l'intero della prima posizione dell'array (ciò significa array [0]).

Quindi, quando dici che l'array [-1] è come riferirsi all'intero salvato in memoria che è appena prima dell'array [0], che nel nostro è, ipoteticamente, intero a. In realtà, questo non è esattamente il modo in cui le variabili sono archiviate in memoria.


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
};;

Benvenuto in CS.SE! Cerchiamo risposte fornite con una spiegazione o una descrizione della lettura. Non siamo un sito di codifica e non vogliamo risposte che siano solo un blocco di codice. È possibile valutare se è possibile modificare la risposta per fornire quel tipo di informazioni. Grazie!
DW
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.