Con gli array, perché è il caso di un [5] == 5 [a]?



Answers

Perché l'accesso dell'array è definito in termini di puntatori. a[i] è definito come *(a + i) , che è commutativo.

Question

Come sottolinea Joel in podcast # 34 , in C Programming Language (aka: K & R), c'è menzione di questa proprietà degli array in C: a[5] == 5[a]

Joel dice che è a causa dell'aritmetica del puntatore, ma io ancora non capisco. Perché a[5] == 5[a] ?




So che la domanda è stata risolta, ma non ho potuto resistere alla condivisione di questa spiegazione.

Ricordo il disegno Principles of Compiler, Supponiamo che sia un array int e che la dimensione di int sia 2 byte, e l'indirizzo di base di a sia 1000.

Come funziona a[5] ->

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

Così,

Allo stesso modo quando il codice c è scomposto in un codice a 3 indirizzi, 5[a] diventerà ->

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

Quindi fondamentalmente entrambe le istruzioni puntano alla stessa posizione in memoria e quindi, a[5] = 5[a] .

Questa spiegazione è anche la ragione per cui gli indici negativi negli array funzionano in C.

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

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.




Ho appena scoperto che questa brutta sintassi potrebbe essere "utile", o almeno molto divertente da giocare quando si vuole gestire una serie di indici che si riferiscono a posizioni nello stesso array. Può sostituire parentesi quadre annidate e rendere il codice più leggibile!

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  

           cout << a[a[a[i]]] << endl;
           // ... is equivalent to ... 
           cout << i[a][a][a] << endl;  // but I prefer this one, it's easier to increase the level of indirection (without loop)

}

Certo, sono abbastanza sicuro che non ci sia un caso d'uso per questo nel codice reale, ma l'ho trovato comunque interessante :)




Non una risposta, ma solo qualche spunto di riflessione. Se la classe sta sovraccaricando l'operatore indice / pedice, l'espressione 0[x] non funzionerà:

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

Dal momento che non abbiamo accesso alla classe int , questo non può essere fatto:

class int
{
   int operator[](const Sub&);
};



Per rispondere alla domanda alla lettera. Non è sempre vero che x == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

stampe

false



nel compilatore c

a[i]
i[a]
*(a+i)

sono diversi modi per fare riferimento a un elemento in un array! (NON A TUTTO IL WEIRD)




Bene, questa è una funzionalità che è possibile solo grazie al supporto della lingua.

Il compilatore interpreta a[i] come *(a+i) e l'espressione 5[a] valuta su *(5+a) . Poiché l'addizione è commutativa, risulta che entrambi sono uguali. Quindi l'espressione è true .




Penso che qualcosa venga perso dalle altre risposte.

Sì, p[i] è per definizione equivalente a *(p+i) , che (poiché l'addizione è commutativa) è equivalente a *(i+p) , che (ancora, con la definizione dell'operatore [] ) è equivalente a i[p] .

(E array[i] , il nome dell'array viene convertito implicitamente in un puntatore al primo elemento dell'array.)

Ma la commutatività dell'aggiunta non è così ovvia in questo caso.

Quando entrambi gli operandi sono dello stesso tipo o anche di tipi numerici diversi che vengono promossi a un tipo comune, la commutatività ha perfettamente senso: x + y == y + x .

Ma in questo caso stiamo parlando specificamente dell'aritmetica del puntatore, in cui un operando è un puntatore e l'altro è un numero intero. (Il numero intero + intero è un'operazione diversa e il puntatore + puntatore non ha senso).

La descrizione dello standard C dell'operatore + ( N1570 6.5.6) dice:

Inoltre, entrambi gli operandi devono avere un tipo aritmetico, oppure un operando deve essere un puntatore a un tipo di oggetto completo e l'altro deve avere un tipo intero.

Avrebbe potuto facilmente dire:

Inoltre, entrambi gli operandi devono avere un tipo aritmetico, oppure l' operando di sinistra deve essere un puntatore a un tipo di oggetto completo e l' operando di destra deve avere un tipo intero.

nel qual caso entrambi i + p e i[p] sarebbero illegali.

In termini C ++, abbiamo due gruppi di operatori overload + , che possono essere descritti genericamente come:

pointer operator+(pointer p, integer i);

e

pointer operator+(integer i, pointer p);

di cui solo il primo è veramente necessario.

Allora, perché è così?

C ++ ha ereditato questa definizione da C, che l'ha ottenuta da B (la commutatività dell'indicizzazione dell'array è esplicitamente menzionata nel Riferimento degli utenti del 1972 a B ), che lo ha ottenuto da BCPL (manuale del 1967), che potrebbe averlo ottenuto anche da lingue precedenti (CPL? Algol?).

Quindi l'idea che l'indicizzazione dell'array sia definita in termini di addizione e quell'aggiunta, anche di un puntatore e di un intero, è commutativa, risale a molti decenni, alle lingue degli antenati di C.

Quelle lingue erano molto meno tipizzate rispetto alla moderna C. In particolare, la distinzione tra puntatori e numeri interi veniva spesso ignorata. (I primi programmatori C a volte usavano i puntatori come numeri interi senza segno, prima che la parola chiave unsigned venisse aggiunta alla lingua.) Quindi l'idea di rendere l'aggiunta non commutativa perché gli operandi sono di tipi diversi probabilmente non si sarebbero verificati per i progettisti di quelle lingue . Se un utente volesse aggiungere due "cose", se quelle "cose" sono numeri interi, puntatori o qualcos'altro, non era compito della lingua impedirlo.

E nel corso degli anni, qualsiasi modifica a tale regola avrebbe infranto il codice esistente (sebbene lo standard ANSI C del 1989 potesse essere una buona opportunità).

Cambiare C e / o C ++ richiedendo di posizionare il puntatore sulla sinistra e il numero intero sulla destra potrebbe rompere qualche codice esistente, ma non ci sarebbe alcuna perdita di reale potenza espressiva.

Così ora abbiamo arr[3] e 3[arr] significano esattamente la stessa cosa, anche se quest'ultima forma non dovrebbe mai apparire al di fuori della IOCCC .




Links