Gli array sono puntatori?

Possibile duplicato:
Il nome dell’array è un puntatore in C?

Array e puntatori sono implementati in modo diverso in C e C ++? Ho trovato questa domanda perché, in entrambi i casi, accediamo agli elementi dall’indirizzo iniziale di un elemento. Quindi, dovrebbe esserci una stretta relazione tra loro. Si prega di spiegare la relazione esatta tra loro. Grazie.

Prendiamo in primo piano le cose importanti: gli array non sono puntatori . I tipi di array e i tipi di puntatori sono cose completamente differenti e vengono trattati in modo diverso dal compilatore.

Dove sorge la confusione è da come C tratta le espressioni di matrice. N1570 :

6.3.2.1 Lvalues, matrici e designatori di funzioni


3 Tranne quando è l’operando dell’operatore sizeof , l’operatore _Alignof , o _Alignof & operator, o è una stringa letterale utilizzata per inizializzare una matrice, un’espressione che ha tipo ” array of type ” viene convertita in un’espressione con il tipo ” puntatore al tipo ” che punta all’elemento iniziale dell’object array e non è un lvalue. Se l’object array ha una class di archiviazione di registro, il comportamento non è definito.

Diamo un’occhiata alle seguenti dichiarazioni:

 int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int *parr = arr; 

arr è una matrice di 10 elementi di int ; si riferisce a un blocco contiguo di memoria abbastanza grande da memorizzare 10 valori int . L’ espressione arr nella seconda dichiarazione è di tipo array, ma poiché non è l’operando di & o sizeof e non è una stringa letterale, il tipo dell’espressione diventa “puntatore a int ” e il valore è l’indirizzo del primo elemento, o &arr[0] .

parr è un puntatore a int; si riferisce a un blocco di memoria abbastanza grande da contenere l’indirizzo di un singolo object int . Inizializzato per puntare al primo elemento in arr come spiegato sopra.

Ecco un’ipotetica mappa di memoria che mostra la relazione tra i due (supponendo 16 bit int e indirizzi a 32 bit):

 Indirizzo dell'object 0x00 0x01 0x02 0x03
 ------ ------- ----------------------
    arr 0x10008000 0x00 0x00 0x00 0x01
                  0x10008004 0x00 0x02 0x00 0x03
                  0x10008008 0x00 0x04 0x00 0x05
                  0x1000800c 0x00 0x06 0x00 0x07
                  0x10008010 0x00 0x08 0x00 0x09
   parr 0x10008014 0x10 0x00 0x80 0x00

I tipi contano cose come sizeof e & ; sizeof arr == 10 * sizeof (int) , che in questo caso è 20, mentre sizeof parr == sizeof (int *) , che in questo caso è 4. Analogamente, il tipo di espressione &arr è int (*)[10] , o un puntatore a una matrice di 10 elementi di int , mentre il tipo di &parr è int ** , o puntatore a puntatore a int .

Si noti che le espressioni arr e &arr produrranno lo stesso valore (l’indirizzo del primo elemento in arr ), ma i tipi delle espressioni sono diversi (rispettivamente int * e int (*)[10] ). Questo fa la differenza quando si usa l’aritmetica del puntatore. Ad esempio, dato:

 int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int *p = arr; int (*ap)[10] = &arr; printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap); p++; ap++; printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap); 

la “prima” linea dovrebbe stampare gli stessi valori per tutte e tre le espressioni (nella nostra ipotetica mappa, 0x10008000 ). La riga “dopo” dovrebbe mostrare tre valori diversi: 0x10008000 , 0x10008002 (base più sizeof (int) ) e 0x10008014 (base più sizeof (int [10]) ).

Ora torniamo al secondo paragrafo precedente: le espressioni degli array vengono convertite in tipi di puntatore nella maggior parte dei casi. Diamo un’occhiata all’espressione in pedice arr[i] . Dato che l’espressione arr non appare come un operando né di sizeof né di & , e poiché non è una stringa letterale utilizzata per inizializzare un altro array, il suo tipo viene convertito da “10-element array of int ” a “pointer to int ” e l’operazione di pedice viene applicata a questo valore del puntatore . In effetti, quando guardi la definizione del linguaggio C, vedi la seguente lingua:

6.5.2.1 Sottoscrizione di array

2 Un’espressione postfissa seguita da un’espressione tra parentesi quadre [] è una designazione abbreviata di un elemento di un object array. La definizione dell’operatore di pedice [] è che E1 [E2] è identico a (* ((E1) + (E2))) . A causa delle regole di conversione applicate all’operatore binario +, se E1 è un object array (equivalentemente, un puntatore all’elemento iniziale di un object array) ed E2 è un numero intero, E1 [E2] designa l’elemento E2 -th di E1 (contando da zero).

In termini pratici, ciò significa che è ansible applicare l’operatore di pedice a un object del puntatore come se fosse una matrice. Questo è il motivo per cui il codice

 int foo(int *p, size_t size) { int sum = 0; int i; for (i = 0; i < size; i++) { sum += p[i]; } return sum; } int main(void) { int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int result = foo(arr, sizeof arr / sizeof arr[0]); ... } 

funziona come fa main si occupa di un array di int , mentre foo che fare con un puntatore a int , eppure entrambi sono in grado di usare l'operatore subscript come se fossero entrambi interessati a un tipo di array.

Significa anche che l'indice della matrice è commutativo : assumendo a è un'espressione di matrice e i è un'espressione intera, a[i] e i[a] sono entrambe espressioni valide, ed entrambe produrranno lo stesso valore.

Non so su C ++. Per C, il c-faq risponde molto meglio di quanto io possa mai.

Piccolo frammento di c-faq:

6.3 Che cosa si intende per “equivalenza di puntatori e matrici” in C?

[…]

In particolare, la chiave di volta dell’equivalenza è questa definizione chiave:

Un riferimento a un object di tipo array-of-T che appare in un’espressione decade (con tre eccezioni) in un puntatore al suo primo elemento; il tipo del puntatore risultante è pointer-to-T.

[…]

In C ++ secondo lo standard C ++ 4.2:

Un lvalue o valore di tipo “array of N T” o “array of unknown bound of T” può essere convertito in un valore di tipo “puntatore a T.” Il risultato è un puntatore al primo elemento dell’array.

No, non sono implementati in modo diverso. Entrambi trovano elementi con lo stesso calcolo: a[i] è all’indirizzo a + i*sizeof(a[0]) , anche p[i] è all’indirizzo p + i*sizeof(p[0]) .

Ma, sono trattati in modo diverso dal sistema di tipi . C ++ ha informazioni di digitazione su array che possono essere viste attraverso l’ sizeof operator (come C), inferenza modello, sovraccarico di funzioni, RTTI e così via. Fondamentalmente, ovunque nella lingua in cui vengono utilizzate le informazioni sul tipo, è ansible che i puntatori e gli array si comportino in modo diverso.

Esistono molti esempi in C ++ in cui due concetti linguistici diversi hanno la stessa implementazione. Solo alcuni: matrici contro puntatori, puntatori contro riferimenti, funzioni virtuali contro puntatori di funzioni, iteratori contro puntatori, per cicli vs cicli while, eccezioni vs longjmp

In ogni caso, c’è una syntax diversa e un modo diverso di pensare ai due concetti, ma alla fine si ottiene lo stesso codice macchina.

In C ++ (e anche in C credo), un array non è un puntatore e può essere dimostrato nel modo seguente.

 #include  int main() { char arr[1000]; std::cout << sizeof arr; } 

se arr era un puntatore, questo programma stamperebbe sizeof (char *) che è tipicamente 4. Ma stampa 1000.

un'altra prova:

 template  void f(T& obj) { T x = obj; //this will fail to compile if T is an array type } int main() { int a[30] = {}; int* p = 0; f(p); //OK f(a); //results in compile error. Remember f takes by ref therefore needs lvalue and no conversion applies } 

Formalmente, un array viene convertito in un puntatore al suo primo elemento nelle conversioni da lvalue a rvalue, ovvero quando un lvalue di tipo array viene fornito in un contesto quando è previsto un valore rval, l'array viene convertito in un puntatore al suo primo elemento.

Inoltre, una funzione dichiarata per acquisire una matrice per valore è equivalente alla funzione di prendere un puntatore, cioè

 void f(int a[]); void f(int a[10]); void f(int* a); 

sono tre dichiarazioni equivalenti. HTH

Nel tipo di array C ++ ha un “attributo di dimensione”, quindi per

 T a[10]; T b[20]; 

a e b ha diversi tipi.

Questo permette di usare un codice come questo

 template void foo(T (&a)[N]) { ... } 

Il più grande punto di confusione tra array e puntatori deriva dalla decisione di K & R di fare in modo che i parametri di funzione dichiarati come tipo di array si comportino come se fossero stati dichiarati come puntatori. Le dichiarazioni

  void foo (int a []); 

e

  void foo (int * a); 

sono equivalenti, come è (per quanto posso dire)

  void foo (int a [5]); 

sebbene non sia positivo, un compilatore dovrebbe accettare un riferimento a un [6] all’interno di quest’ultima funzione. In altri contesti, una dichiarazione di matrice alloca lo spazio per il numero indicato di elementi. Si noti che:

 typedef int foo [1];

qualsiasi dichiarazione di un tipo foo assegnerà spazio per un elemento, ma ogni tentativo di passare foo come parametro di funzione passerà invece l’indirizzo. Qualcosa di utile trucco che ho imparato studiando un’implementazione va_list.