Il nome di un array è un puntatore in C? In caso contrario, qual è la differenza tra il nome di un array e una variabile puntatore?
&array[0]
produce un puntatore, non un array;)
Il nome di un array è un puntatore in C? In caso contrario, qual è la differenza tra il nome di un array e una variabile puntatore?
&array[0]
produce un puntatore, non un array;)
Risposte:
Un array è un array e un puntatore è un puntatore, ma nella maggior parte dei casi i nomi degli array vengono convertiti in puntatori. Un termine spesso usato è che decadono in puntatori.
Ecco un array:
int a[7];
a
contiene spazio per sette numeri interi e puoi inserire un valore in uno di essi con un compito, in questo modo:
a[3] = 9;
Ecco un puntatore:
int *p;
p
non contiene spazi per numeri interi, ma può puntare a uno spazio per un numero intero. Ad esempio, possiamo impostarlo in modo che punti a uno dei punti dell'array a
, come il primo:
p = &a[0];
Ciò che può confondere è che puoi anche scrivere questo:
p = a;
Questo non copia il contenuto dell'array a
nel puntatore p
(qualunque cosa significhi). Al contrario, il nome dell'array a
viene convertito in un puntatore al suo primo elemento. Quindi quel compito fa lo stesso del precedente.
Ora puoi usare p
in modo simile a un array:
p[3] = 17;
Il motivo per cui funziona è che l'operatore di dereferenziazione dell'array in C [ ]
, è definito in termini di puntatori. x[y]
significa: inizia con il puntatore x
, y
fai avanzare gli elementi dopo ciò a cui punta il puntatore, quindi prendi tutto quello che c'è. Utilizzando la sintassi aritmetica del puntatore, si x[y]
può anche scrivere come *(x+y)
.
Affinché ciò funzioni con un array normale, come il nostro a
, il nome a
in a[3]
deve prima essere convertito in un puntatore (nel primo elemento in a
). Quindi avanziamo di 3 elementi e prendiamo qualunque cosa ci sia. In altre parole: prendere l'elemento nella posizione 3 dell'array. (Qual è il quarto elemento dell'array, poiché il primo è numerato 0.)
Quindi, in sintesi, i nomi di array in un programma C vengono (nella maggior parte dei casi) convertiti in puntatori. Un'eccezione è quando utilizziamo l' sizeof
operatore su un array. Se a
fosse convertito in un puntatore in questo contesto, sizeof a
darebbe le dimensioni di un puntatore e non dell'array reale, il che sarebbe piuttosto inutile, quindi in quel caso a
significa l'array stesso.
functionpointer()
e (*functionpointer)()
significano la stessa cosa, stranamente.
sizeof()
l'altro contesto in cui non esiste un decadimento del puntatore array-> è l'operatore &
- nel tuo esempio sopra, &a
sarà un puntatore a un array di 7 int
, non un puntatore a un singolo int
; cioè, sarà il suo tipo int(*)[7]
, a cui non è implicitamente convertibile int*
. In questo modo, le funzioni possono effettivamente portare i puntatori ad array di dimensioni specifiche e applicare la restrizione tramite il sistema di tipi.
Quando un array viene utilizzato come valore, il suo nome rappresenta l'indirizzo del primo elemento.
Quando un array non viene utilizzato come valore, il suo nome rappresenta l'intero array.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Se un'espressione di tipo array (come il nome dell'array) appare in un'espressione più grande e non è l'operando dell'operatore &
o sizeof
, allora il tipo dell'espressione array viene convertito da "array N-element di T" in "pointer to T", e il valore dell'espressione è l'indirizzo del primo elemento dell'array.
In breve, il nome dell'array non è un puntatore, ma nella maggior parte dei contesti viene trattato come se fosse un puntatore.
modificare
Rispondere alla domanda nel commento:
Se uso sizeof, conto la dimensione dei soli elementi dell'array? Quindi l'array "head" occupa anche spazio con le informazioni sulla lunghezza e un puntatore (e questo significa che occupa più spazio di un normale puntatore)?
Quando si crea un array, l'unico spazio allocato è lo spazio per gli elementi stessi; non viene materializzata alcuna memoria per un puntatore separato o metadati. Dato
char a[10];
quello che ottieni in memoria è
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
L' espressione si a
riferisce all'intero array, ma non esiste alcun oggetto a
separato dagli elementi dell'array stessi. Pertanto, sizeof a
fornisce la dimensione (in byte) dell'intero array. L'espressione &a
fornisce l'indirizzo dell'array, che è lo stesso dell'indirizzo del primo elemento . La differenza tra &a
e &a[0]
è il tipo del risultato 1 - char (*)[10]
nel primo caso e char *
nel secondo.
Il punto in cui le cose si fanno strane è quando si desidera accedere a singoli elementi - l'espressione a[i]
è definita come il risultato di *(a + i)
- dato un valore di indirizzo a
, i
elementi di offset ( non byte ) da quell'indirizzo e dereference il risultato.
Il problema è che a
non è un puntatore o un indirizzo: è l'intero oggetto array. Pertanto, la regola in C che ogni volta che il compilatore vede un'espressione di tipo array (come a
, che ha tipo char [10]
) e quell'espressione non è l'operando degli operatori sizeof
unari o &
, il tipo di quell'espressione viene convertito ("decadimenti") a un tipo di puntatore ( char *
) e il valore dell'espressione è l'indirizzo del primo elemento dell'array. Pertanto, l' espressione a
ha lo stesso tipo e valore dell'espressione &a[0]
(e, per estensione, l'espressione *a
ha lo stesso tipo e valore dell'espressione a[0]
).
C è stato derivato da un linguaggio in precedenza chiamato B, e in B a
è stato un oggetto puntatore separato dagli elementi dell'array a[0]
, a[1]
ecc Ritchie ha voluto mantenere la semantica di array di B, ma non voleva fare confusione con l'archiviazione l'oggetto puntatore separato. Quindi se ne è liberato. Al contrario, il compilatore convertirà le espressioni di matrice in espressioni di puntatore durante la traduzione, se necessario.
Ricorda che ho detto che le matrici non memorizzano metadati delle loro dimensioni. Non appena quell'espressione di matrice "decade" in un puntatore, tutto ciò che hai è un puntatore a un singolo elemento. Quell'elemento può essere il primo di una sequenza di elementi oppure può essere un singolo oggetto. Non c'è modo di saperlo in base al puntatore stesso.
Quando si passa un'espressione di array a una funzione, tutta la funzione che riceve è un puntatore al primo elemento - non ha idea di quanto sia grande l'array (ecco perché la gets
funzione era una tale minaccia e alla fine è stata rimossa dalla libreria). Affinché la funzione sappia quanti elementi ha l'array, è necessario utilizzare un valore sentinella (come il terminatore 0 nelle stringhe C) oppure passare il numero di elementi come parametro separato.
sizeof
è un operatore e valuta il numero di byte nell'operando (un'espressione che indica un oggetto o un nome di tipo tra parentesi). Quindi, per un array, sizeof
valuta il numero di elementi moltiplicato per il numero di byte in un singolo elemento. Se an ha int
una larghezza di 4 byte, una matrice di 5 elementi int
occupa 20 byte.
[ ]
speciale? Ad esempio, int a[2][3];
quindi per x = a[1][2];
, sebbene possa essere riscritto come x = *( *(a+1) + 2 );
, qui a
non viene convertito in un tipo di puntatore int*
(sebbene se a
è un argomento di una funzione in cui dovrebbe essere convertito int*
).
a
ha tipo int [2][3]
, che "decade" da digitare int (*)[3]
. L'espressione *(a + 1)
ha tipo int [3]
, che "decade" int *
. Quindi, *(*(a + 1) + 2)
avrà tipo int
. a
punta al primo array di 3 elementi di int
, a + 1
punta al secondo array di 3 elementi di int
, *(a + 1)
è il secondo array di 3 elementi di int
, *(a + 1) + 2
punta al terzo elemento del secondo array di int
, quindi *(*(a + 1) + 2)
è il terzo elemento del secondo array di int
. Il modo in cui viene mappato al codice macchina dipende interamente dal compilatore.
Un array dichiarato come questo
int a[10];
alloca memoria per 10 int
s. Non è possibile modificare a
ma è possibile eseguire l'aritmetica del puntatore con a
.
Un puntatore come questo alloca memoria solo per il puntatore p
:
int *p;
Non alloca alcun int
s. Puoi modificarlo:
p = a;
e usa gli array di array come puoi con un:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
s con durata di memorizzazione automatica".
Il nome dell'array da solo fornisce una posizione di memoria, quindi è possibile trattare il nome dell'array come un puntatore:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
E altre cose interessanti che puoi fare al puntatore (ad esempio aggiungendo / sottraendo un offset), puoi anche fare un array:
printf("value at memory location %p is %d", a + 1, *(a + 1));
Dal punto di vista del linguaggio, se C non esponesse l'array come una sorta di "puntatore" (pedanticamente è solo una posizione di memoria. Non può indicare una posizione arbitraria in memoria, né può essere controllata dal programmatore). Dobbiamo sempre codificare questo:
printf("value at memory location %p is %d", &a[1], a[1]);
Penso che questo esempio faccia luce sul problema:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Compila bene (con 2 avvertimenti) in gcc 4.9.2 e stampa quanto segue:
a == &a: 1
oops :-)
Quindi, la conclusione è no, l'array non è un puntatore, non è archiviato in memoria (nemmeno uno di sola lettura) come puntatore, anche se sembra che sia, poiché puoi ottenere il suo indirizzo con l'operatore & . Ma - oops - quell'operatore non funziona :-)), in entrambi i casi, sei stato avvisato:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ rifiuta tali tentativi con errori in fase di compilazione.
Modificare:
Questo è ciò che intendevo dimostrare:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Anche se c
e a
"punta" alla stessa memoria, è possibile ottenere l'indirizzo del c
puntatore, ma non è possibile ottenere l'indirizzo del a
puntatore.
-std=c11 -pedantic-errors
, si ottiene un errore del compilatore per la scrittura di codice C non valido. Il motivo è perché si tenta di assegnare int (*)[3]
a una variabile di int**
, che sono due tipi che non hanno assolutamente nulla a che fare l'uno con l'altro. Quindi, cosa dovrebbe dimostrare questo esempio, non ne ho idea.
int **
tipo non è il punto lì, si dovrebbe usare meglio void *
per questo.
Il nome dell'array si comporta come un puntatore e punta al primo elemento dell'array. Esempio:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Entrambe le istruzioni di stampa forniranno esattamente lo stesso risultato per una macchina. Nel mio sistema ha dato:
0x7fff6fe40bc0
Un array è una raccolta di elementi secuential e contigui in memoria. In C il nome di un array è l'indice del primo elemento e applicando un offset è possibile accedere al resto degli elementi. Un "indice del primo elemento" è in effetti un puntatore a una direzione della memoria.
La differenza con le variabili del puntatore è che non è possibile modificare la posizione a cui punta il nome dell'array, quindi è simile a un puntatore const (è simile, non è lo stesso. Vedere il commento di Mark). Ma anche che non è necessario dereferenziare il nome dell'array per ottenere il valore se si utilizza l'aritmetica del puntatore:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
Quindi la risposta è un po 'sì.
Il nome dell'array è l'indirizzo del primo elemento di un array. Quindi sì il nome dell'array è un puntatore const.