Come mai l'indirizzo di un array è uguale al suo valore in C?


189

Nel seguente bit di codice, i valori e gli indirizzi dei puntatori differiscono come previsto.

Ma i valori e gli indirizzi dell'array no!

Come può essere?

Produzione

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}

Dalle FAQ di comp.lang.c: - [Quindi cosa si intende per `` equivalenza di puntatori e matrici '' in C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Dato che i riferimenti di array decadono in puntatori, se arr è un array, qual è la differenza tra arr e & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) Oppure vai a leggere l'intera sezione Array e puntatori .
Jamesdlin,

3
Avevo aggiunto una risposta con diagramma a questa domanda due anni fa. Cosa sizeof(&array)ritorna?
Grijesh Chauhan,

Risposte:


214

Il nome di un array valuta solitamente all'indirizzo del primo elemento della matrice, in modo arraye &arrayhanno lo stesso valore (ma diverse tipologie, così array+1e &array+1sarà non essere uguali se la matrice è più di 1 elemento lungo).

Esistono due eccezioni a ciò: quando il nome dell'array è un operando di sizeofo unario &(indirizzo di), il nome si riferisce all'oggetto array stesso. Così sizeof arraysi dà la dimensione in byte del intero array, non la dimensione di un puntatore.

Per un array definito come T array[size], avrà tipo T *. Quando / se lo si incrementa, si arriva all'elemento successivo nella matrice.

&arrayrestituisce lo stesso indirizzo, ma data la stessa definizione, crea un puntatore del tipo T(*)[size], ovvero è un puntatore a un array, non a un singolo elemento. Se si incrementa questo puntatore, verrà aggiunta la dimensione dell'intero array, non la dimensione di un singolo elemento. Ad esempio, con un codice come questo:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Possiamo aspettarci che il secondo puntatore sia 16 maggiore del primo (perché è un array di 16 caratteri). Poiché% p in genere converte i puntatori in esadecimali, potrebbe assomigliare a:

0x12341000    0x12341010

3
@Alexandre: &arrayè un puntatore al primo elemento dell'array, dove si arrayriferisce all'intero array. La differenza fondamentale può anche essere osservata confrontando sizeof(array), con sizeof(&array). Si noti tuttavia che se si passa arraycome argomento a una funzione, &arrayin realtà viene passato solo. Non è possibile passare un array per valore a meno che non sia incapsulato con a struct.
Clifford,

14
@Clifford: se si passa un array a una funzione, decade a un puntatore al suo primo elemento in modo così efficace che &array[0]viene passato, non &arrayquale sarebbe un puntatore a un array. Potrebbe essere un gioco da ragazzi ma penso che sia importante chiarire; i compilatori avviseranno se la funzione ha un prototipo che corrisponde al tipo di puntatore passato.
CB Bailey

2
@Jerry Coffin Ad esempio int * p = & a, se voglio l'indirizzo di memoria del puntatore int p, posso fare & p. Poiché & array si converte nell'indirizzo dell'intero array (che inizia all'indirizzo del primo elemento). Quindi come posso trovare l'indirizzo di memoria del puntatore dell'array (che memorizza l'indirizzo del primo elemento nell'array)? Deve essere da qualche parte nella memoria, giusto?
John Lee,

2
@JohnLee: No, non è necessario che vi sia un puntatore all'array in qualsiasi punto della memoria. Se si crea un puntatore, si può quindi prendere il suo indirizzo: int *p = array; int **pp = &p;.
Jerry Coffin,

3
@Clifford il primo commento è sbagliato, perché conservarlo ancora? Penso che potrebbe portare a malintesi per coloro che non leggono la seguente risposta (@Charles).
Rick,

30

Questo perché il nome dell'array ( my_array) è diverso da un puntatore all'array. È un alias dell'indirizzo di un array e il suo indirizzo è definito come l'indirizzo dell'array stesso.

Il puntatore è una normale variabile C nello stack, tuttavia. Quindi, puoi prendere il suo indirizzo e ottenere un valore diverso dall'indirizzo che contiene.

Ho scritto su questo argomento qui - per favore dai un'occhiata.


& My_array non dovrebbe essere un'operazione non valida poiché il valore di my_array non è nello stack, lo sono solo my_array [0 ... lunghezza]? Quindi avrebbe tutto senso ...
Alexandre,

@Alexandre: non sono sicuro del perché sia ​​permesso, in realtà.
Eli Bendersky,

Puoi prendere l'indirizzo di qualsiasi variabile (se non contrassegnata register) qualunque sia la sua durata di memorizzazione: statica, dinamica o automatica.
CB Bailey,

my_arraystesso è nello stack, perché my_array è l'intero array.
Caf

3
my_array, quando non è l'oggetto dell'operatore &o sizeof, viene valutato come un puntatore al suo primo elemento (cioè. &my_array[0]) - ma di per my_arraynon è quel puntatore ( my_arrayè ancora l'array). Quel puntatore è solo un valore effimero (es. Dato int a;, è proprio come a + 1) - concettualmente almeno è "calcolato secondo necessità". Il vero "valore" di my_arrayè il contenuto dell'intero array - è solo che fissare questo valore in C è come cercare di catturare la nebbia in un barattolo.
Caf

28

In C, quando si utilizza il nome di un array in un'espressione (incluso il passaggio a una funzione), a meno che non sia l'operando dell'operatore address-of ( &) o sizeofdell'operatore, decade in un puntatore al suo primo elemento.

Cioè, nella maggior parte dei contesti arrayè equivalente &array[0]sia nel tipo che nel valore.

Nel tuo esempio, my_arrayha un tipo char[100]che decade in a char*quando lo passi a printf.

&my_arrayha tipo char (*)[100](puntatore a un array di 100 char). Poiché è l'operando &, questo è uno dei casi che my_arraynon decade immediatamente in un puntatore al suo primo elemento.

Il puntatore alla matrice ha lo stesso valore di indirizzo di un puntatore al primo elemento della matrice poiché un oggetto della matrice è solo una sequenza contigua dei suoi elementi, ma un puntatore a una matrice ha un tipo diverso da un puntatore a un elemento di quella matrice. Questo è importante quando si esegue l'aritmetica del puntatore sui due tipi di puntatore.

pointer_to_arrayha il tipo char *- inizializzato per puntare al primo elemento dell'array poiché è ciò che my_arraydecade nell'espressione dell'inizializzatore - e &pointer_to_array ha il tipo char **(puntatore a un puntatore a a char).

Di questi: my_array(dopo il decadimento a char*), &my_arraye pointer_to_arraytutti puntano direttamente sull'array o sul primo elemento dell'array e quindi hanno lo stesso valore di indirizzo.


3

Il motivo per cui my_arraye &my_arrayrisulta nello stesso indirizzo può essere facilmente compreso osservando il layout di memoria di un array.

Supponiamo che tu abbia una matrice di 10 caratteri (anziché i 100 nel tuo codice).

char my_array[10];

La memoria per my_arrayassomiglia a qualcosa del tipo:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

In C / C ++, un array decade con il puntatore al primo elemento in un'espressione come

printf("my_array = %p\n", my_array);

Se esaminate la posizione del primo elemento dell'array, vedrete che il suo indirizzo è uguale all'indirizzo dell'array:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].

3

Nel linguaggio di programmazione B, che era il predecessore immediato di C, i puntatori e gli interi erano liberamente intercambiabili. Il sistema si comporterebbe come se tutta la memoria fosse un array gigantesco. Ogni nome di variabile aveva un indirizzo globale o relativo allo stack associato ad esso, per ogni nome di variabile l'unica cosa che il compilatore doveva tenere traccia era se si trattava di una variabile globale o locale e il suo indirizzo relativo al primo globale o locale variabile.

Data una dichiarazione globale come i;[non vi era alcuna necessità di specificare un tipo, dal momento che tutto era un intero / puntatore] verrà elaborato dal compilatore come: address_of_i = next_global++; memory[address_of_i] = 0;e una dichiarazione come i++verrà elaborato come: memory[address_of_i] = memory[address_of_i]+1;.

Una dichiarazione simile arr[10];verrebbe elaborata come address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Si noti che non appena elaborata tale dichiarazione, il compilatore potrebbe immediatamente dimenticare di arressere un array . Una dichiarazione simile arr[i]=6;verrebbe elaborata come memory[memory[address_of_a] + memory[address_of_i]] = 6;. Al compilatore non importa se arrrappresentasse un array e iun numero intero, o viceversa. In effetti, non gli importerebbe se fossero entrambi array o interi; genererebbe perfettamente felicemente il codice come descritto, indipendentemente dal fatto che il comportamento risultante sia probabilmente utile.

Uno degli obiettivi del linguaggio di programmazione C era di essere ampiamente compatibile con B. In B, il nome di un array [chiamato "vettore" nella terminologia di B] identificava una variabile che conteneva un puntatore che inizialmente era assegnato a puntare a al primo elemento di un'allocazione della dimensione data, quindi se quel nome apparisse nell'elenco degli argomenti per una funzione, la funzione riceverebbe un puntatore al vettore. Anche se C ha aggiunto tipi di array "reali", il cui nome è stato rigidamente associato all'indirizzo dell'allocazione piuttosto che a una variabile di puntatore che inizialmente puntava all'allocazione, avendo gli array si decomponevano in puntatori, il codice che dichiarava un array di tipo C si comportava in modo identico al codice B che ha dichiarato un vettore e quindi non ha mai modificato la variabile con il suo indirizzo.


1

In realtà &myarrayed myarrayentrambi sono l'indirizzo di base.

Se vuoi vedere la differenza invece di usare

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

uso

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
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.