C Language
puntatori
Ricerca…
introduzione
Un puntatore è un tipo di variabile che può memorizzare l'indirizzo di un altro oggetto o una funzione.
Sintassi
- <Tipo di dati> * <Nome variabile>;
- int * ptrToInt;
- void * ptrToVoid; / * C89 + * /
- struct someStruct * ptrToStruct;
- int ** ptrToPtrToInt;
- int arr [lunghezza]; int * ptrToFirstElem = arr; / * Per <C99 'length' deve essere una costante di tempo di compilazione, per> = C11 potrebbe essere necessario essere uno. * /
- int * arrayOfPtrsToInt [lunghezza]; / * Per <C99 'length' deve essere una costante di tempo di compilazione, per> = C11 potrebbe essere necessario essere uno. * /
Osservazioni
La posizione dell'asterisco non influisce sul significato della definizione:
/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;
Tuttavia, quando si definiscono più puntatori contemporaneamente, ognuno richiede il proprio asterisco:
int *i, *j; /* i and j are both pointers */
int* i, j; /* i is a pointer, but j is an int not a pointer variable */
È anche possibile una serie di puntatori, dove viene indicato un asterisco prima del nome della variabile dell'array:
int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */
Errori comuni
L'uso improprio dei puntatori è spesso una fonte di bug che può includere bug di sicurezza o crash del programma, molto spesso a causa di errori di segmentazione.
Non verificare i problemi di allocazione
L'allocazione della memoria non è garantita e può invece restituire un puntatore NULL
. L'utilizzo del valore restituito, senza verificare se l'allocazione ha esito positivo, richiama il comportamento non definito . Questo di solito porta a un arresto anomalo, ma non è garantito che si verifichi un arresto anomalo, quindi fare affidamento su questo può anche portare a problemi.
Ad esempio, modo pericoloso:
struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */
Modo sicuro:
struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
s->someValue = 0; /* This is safe, we have checked that s is valid */
}
Utilizzando numeri letterali invece di sizeof quando si richiede memoria
Per una determinata configurazione di compilatore / macchina, i tipi hanno una dimensione nota; tuttavia, non esiste uno standard che definisce che la dimensione di un dato tipo (diversa da char
) sarà la stessa per tutte le configurazioni del compilatore / macchina. Se il codice utilizza 4 invece di sizeof(int)
per l'allocazione di memoria, potrebbe funzionare sulla macchina originale, ma il codice non è necessariamente portabile ad altre macchine o compilatori. Le dimensioni fisse per i tipi devono essere sostituite da sizeof(that_type)
o sizeof(*var_ptr_to_that_type)
.
Assegnazione non portatile:
int *intPtr = malloc(4*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(8*1000); /* allocating storage for 1000 long */
Assegnazione portatile:
int *intPtr = malloc(sizeof(int)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(long)*1000); /* allocating storage for 1000 long */
O, meglio ancora:
int *intPtr = malloc(sizeof(*intPtr)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(*longPtr)*1000); /* allocating storage for 1000 long */
Perdite di memoria
Mancata allocazione della memoria utilizzando lead free
a un accumulo di memoria non riutilizzabile, che non è più utilizzato dal programma; questo è chiamato una perdita di memoria . Le perdite di memoria sprecano risorse di memoria e possono portare a problemi di allocazione.
Errori logici
Tutte le allocazioni devono seguire lo stesso schema:
- Assegnazione tramite
malloc
(ocalloc
) - Utilizzo per memorizzare i dati
- Ridistribuzione utilizzando
free
La mancata aderenza a questo schema, come l'uso della memoria dopo una chiamata a free
( puntatore penzolante ) o prima di una chiamata a malloc
( puntatore jolly ), chiamata free
due volte ("double free"), ecc., Solitamente causa un errore di segmentazione e provoca un arresto anomalo del programma.
Questi errori possono essere transitori e difficili da debugare: ad esempio, la memoria liberata di solito non viene immediatamente recuperata dal sistema operativo, e quindi i puntatori penzolanti possono persistere per un po 'e sembrano funzionare.
Sui sistemi in cui funziona, Valgrind è uno strumento prezioso per identificare quale memoria è trapelata e dove è stata originariamente allocata.
Creazione di puntatori per impilare le variabili
La creazione di un puntatore non prolunga la vita della variabile puntata. Per esempio:
int* myFunction()
{
int x = 10;
return &x;
}
Qui, x
ha durata di archiviazione automatica (comunemente nota come allocazione dello stack ). Poiché è allocata nello stack, la sua durata è limitata a quando myFunction
è in esecuzione; dopo che myFunction
è uscito, la variabile x
viene distrutta. Questa funzione ottiene l'indirizzo di x
(usando &x
), e lo restituisce al chiamante, lasciando il chiamante con un puntatore a una variabile inesistente. Il tentativo di accedere a questa variabile invocherà quindi un comportamento non definito .
La maggior parte dei compilatori non cancella un frame di stack dopo che la funzione è stata chiusa, quindi il dereferenziamento del puntatore restituito spesso fornisce i dati previsti. Tuttavia, quando viene chiamata un'altra funzione, la memoria puntata potrebbe essere sovrascritta e sembra che i dati puntati siano stati danneggiati.
Per risolvere questo, o malloc
lo spazio di archiviazione per la variabile da restituire e restituire un puntatore allo spazio di archiviazione appena creato oppure richiedere che venga passato un puntatore valido alla funzione anziché restituirlo, ad esempio:
#include <stdlib.h>
#include <stdio.h>
int *solution1(void)
{
int *x = malloc(sizeof *x);
if (x == NULL)
{
/* Something went wrong */
return NULL;
}
*x = 10;
return x;
}
void solution2(int *x)
{
/* NB: calling this function with an invalid or null pointer
causes undefined behaviour. */
*x = 10;
}
int main(void)
{
{
/* Use solution1() */
int *foo = solution1();
if (foo == NULL)
{
/* Something went wrong */
return 1;
}
printf("The value set by solution1() is %i\n", *foo);
/* Will output: "The value set by solution1() is 10" */
free(foo); /* Tidy up */
}
{
/* Use solution2() */
int bar;
solution2(&bar);
printf("The value set by solution2() is %i\n", bar);
/* Will output: "The value set by solution2() is 10" */
}
return 0;
}
Incremento / decremento e dereferenziazione
Se scrivi *p++
per incrementare ciò che è indicato da p
, hai torto.
Post incremento / decremento viene eseguito prima del dereferenziamento. Pertanto, questa espressione incrementerà il puntatore p
stesso e restituirà ciò che è stato indicato da p
prima di incrementarlo senza modificarlo.
Dovresti scrivere (*p)++
per incrementare ciò che è indicato da p
.
Questa regola si applica anche al post decremento: *p--
diminuirà il puntatore p
stesso, non quello che viene indicato da p
.
Dereferenziare un puntatore
int a = 1;
int *a_pointer = &a;
Per dereferenziare a_pointer
e modificare il valore di a, usiamo la seguente operazione
*a_pointer = 2;
Questo può essere verificato usando le seguenti dichiarazioni di stampa.
printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */
Tuttavia, si sarebbe errato dereferenziare un puntatore NULL
o altrimenti non valido. Questo
int *p1, *p2;
p1 = (int *) 0xbad;
p2 = NULL;
*p1 = 42;
*p2 = *p1 + 1;
è di solito un comportamento indefinito . p1
non può essere dereferenziato perché punta a un indirizzo 0xbad
che potrebbe non essere un indirizzo valido. Chissà cosa c'è? Potrebbe essere la memoria del sistema operativo o la memoria di un altro programma. L'unico codice temporale come questo è usato, è nello sviluppo embedded, che memorizza informazioni particolari su indirizzi hard-coded. p2
non può essere dereferenziato perché è NULL
, che non è valido.
Dereferenziare un puntatore a una struttura
Diciamo che abbiamo la seguente struttura:
struct MY_STRUCT
{
int my_int;
float my_float;
};
Possiamo definire MY_STRUCT
per omettere la parola chiave struct
modo da non dover scrivere struct MY_STRUCT
ogni volta che la usiamo. Questo, tuttavia, è facoltativo.
typedef struct MY_STRUCT MY_STRUCT;
Se poi abbiamo un puntatore a un'istanza di questa struttura
MY_STRUCT *instance;
Se questa istruzione appare nell'ambito del file, l' instance
verrà inizializzata con un puntatore nullo all'avvio del programma. Se questa istruzione appare all'interno di una funzione, il suo valore non è definito. La variabile deve essere inizializzata in modo che punti a una variabile MY_STRUCT
valida o a spazio assegnato dinamicamente, prima che possa essere dereferenziata. Per esempio:
MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;
Quando il puntatore è valido, possiamo dereferenziarlo per accedere ai suoi membri usando una delle due diverse notazioni:
int a = (*instance).my_int;
float b = instance->my_float;
Mentre entrambi questi metodi funzionano, è preferibile utilizzare l'operatore freccia ->
anziché la combinazione di parentesi, l'operatore dereferenziazione *
e il punto .
operatore perché è più facile da leggere e capire, specialmente con gli usi annidati.
Un'altra importante differenza è mostrata di seguito:
MY_STRUCT copy = *instance;
copy.my_int = 2;
In questo caso, la copy
contiene una copia del contenuto instance
. Cambiare my_int
di copy
non lo cambierà in instance
.
MY_STRUCT *ref = instance;
ref->my_int = 2;
In questo caso, ref
è un riferimento instance
. Cambiando my_int
usando il riferimento lo cambieremo in instance
.
È pratica comune utilizzare i puntatori alle strutture come parametri nelle funzioni, piuttosto che le strutture stesse. L'utilizzo delle structs come parametri di funzione potrebbe causare un overflow dello stack se la struttura è grande. L'utilizzo di un puntatore su una struttura utilizza solo uno spazio sufficiente per il puntatore, ma può causare effetti secondari se la funzione modifica la struttura passata nella funzione.
Puntatori di funzione
I puntatori possono anche essere usati per indicare le funzioni.
Prendiamo una funzione di base:
int my_function(int a, int b) { return 2 * a + 3 * b; }
Ora definiamo un puntatore del tipo di quella funzione:
int (*my_pointer)(int, int);
Per crearne uno, usa questo modello:
return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)
Quindi dobbiamo assegnare questo puntatore alla funzione:
my_pointer = &my_function;
Questo puntatore può ora essere utilizzato per chiamare la funzione:
/* Calling the pointed function */ int result = (*my_pointer)(4, 2); ... /* Using the function pointer as an argument to another function */ void another_function(int (*another_pointer)(int, int)) { int a = 4; int b = 2; int result = (*another_pointer)(a, b); printf("%d\n", result); }
Sebbene questa sintassi sembri più naturale e coerente con i tipi di base, i puntatori alle funzioni di attribuzione e dereferenziazione non richiedono l'uso degli operatori &
e *
. Quindi il seguente snippet è ugualmente valido:
/* Attribution without the & operator */ my_pointer = my_function; /* Dereferencing without the * operator */ int result = my_pointer(4, 2);
Per aumentare la leggibilità dei puntatori di funzione, è possibile utilizzare typedef.
typedef void (*Callback)(int a); void some_function(Callback callback) { int a = 4; callback(a); }
Un altro trucco di leggibilità è che lo standard C consente di semplificare un puntatore a funzione in argomenti come sopra (ma non nella dichiarazione di variabili) a qualcosa che assomiglia a un prototipo di funzione; quindi il seguente può essere usato in modo equivalente per le definizioni e le dichiarazioni di funzioni:
void some_function(void callback(int))
{
int a = 4;
callback(a);
}
Guarda anche
Inizializzazione dei puntatori
L'inizializzazione del puntatore è un buon modo per evitare i puntatori selvaggi. L'inizializzazione è semplice e non è diversa dall'inizializzazione di una variabile.
#include <stddef.h>
int main()
{
int *p1 = NULL;
char *p2 = NULL;
float *p3 = NULL;
/* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */
...
}
Nella maggior parte dei sistemi operativi, l'utilizzo involontario di un puntatore che è stato inizializzato su NULL
spesso causa l'arresto immediato del programma immediatamente, facilitando l'identificazione della causa del problema. L'utilizzo di un puntatore non inizializzato può spesso causare errori di diagnostica.
Attenzione:
Il risultato del dereferenziamento di un puntatore NULL
non è definito, pertanto non causerà necessariamente un arresto anomalo anche se questo è il comportamento naturale del sistema operativo su cui è in esecuzione il programma. Le ottimizzazioni del compilatore possono mascherare l'arresto anomalo, causare l'arresto anomalo prima o dopo il punto nel codice sorgente in cui si è verificata la deviazione del puntatore nullo o causare la rimozione inaspettata dal programma di parti del codice che contengono il riferimento del puntatore nullo. Generalmente, i build di debug non mostrano questi comportamenti, ma ciò non è garantito dallo standard di lingua. Sono ammessi anche altri comportamenti imprevisti e / o indesiderati.
Poiché NULL
non punta mai a una variabile, a una memoria allocata oa una funzione, è sicuro da utilizzare come valore di guardia.
Attenzione:
Solitamente NULL
è definito come (void *)0
. Ma questo non implica che l'indirizzo di memoria assegnato sia 0x0
. Per ulteriori chiarimenti, fai riferimento a C-faq per i puntatori NULL
Si noti che è anche possibile inizializzare i puntatori per contenere valori diversi da NULL.
int i1;
int main()
{
int *p1 = &i1;
const char *p2 = "A constant string to point to";
float *p3 = malloc(10 * sizeof(float));
}
Indirizzo-dell'operatore (&)
Per qualsiasi oggetto (cioè variabile, matrice, unione, struttura, puntatore o funzione) l'operatore di indirizzo unario può essere utilizzato per accedere all'indirizzo di quell'oggetto.
Supporre che
int i = 1;
int *p = NULL;
Quindi una dichiarazione p = &i;
, copia l'indirizzo della variabile i
nel puntatore p
.
È espresso come p
punti a i
.
printf("%d\n", *p);
stampa 1, che è il valore di i
.
Puntatore aritmetico
Vedi qui: Pointer Arithmetic
void * puntatori come argomenti e valori di ritorno alle funzioni standard
void*
è un tipo catch all per i puntatori ai tipi di oggetto. Un esempio di questo in uso è con la funzione malloc
, che è dichiarata come
void* malloc(size_t);
Il tipo restituito da puntatore a vuoto indica che è possibile assegnare il valore restituito da malloc
a un puntatore a qualsiasi altro tipo di oggetto:
int* vector = malloc(10 * sizeof *vector);
Generalmente è considerata una buona pratica non inserire esplicitamente i valori dentro e fuori i puntatori void. Nel caso specifico di malloc()
questo è dovuto al fatto che con un cast esplicito, il compilatore può assumere, ma non avvisare, un tipo di ritorno errato per malloc()
, se si dimentica di includere stdlib.h
. È anche il caso di usare il comportamento corretto dei puntatori void per conformarsi meglio al principio DRY (non ripeterlo); confrontare il precedente con il seguente, in cui il seguente codice contiene diversi posti aggiuntivi inutili in cui un errore di battitura potrebbe causare problemi:
int* vector = (int*)malloc(10 * sizeof int*);
Allo stesso modo, funzioni come
void* memcpy(void *restrict target, void const *restrict source, size_t size);
avere i loro argomenti specificati come void *
perché l'indirizzo di qualsiasi oggetto, indipendentemente dal tipo, può essere passato. Qui anche una chiamata non dovrebbe usare un cast
unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);
Puntatori di Const
Puntatori singoli
Puntatore a un
int
Il puntatore può puntare a numeri interi diversi e gli
int
possono essere modificati tramite il puntatore. Questo esempio di codice assegna b a puntoint b
quindi cambia il valore dib
a100
.int b; int* p; p = &b; /* OK */ *p = 100; /* OK */
Puntatore a un
const int
Il puntatore può puntare a numeri interi diversi ma il valore di
int
non può essere modificato tramite il puntatore.int b; const int* p; p = &b; /* OK */ *p = 100; /* Compiler Error */
const
puntatore aint
Il puntatore può solo puntare a un
int
ma il valore diint
può essere modificato tramite il puntatore.int a, b; int* const p = &b; /* OK as initialisation, no assignment */ *p = 100; /* OK */ p = &a; /* Compiler Error */
const
puntatore aconst int
Il puntatore può solo puntare a un
int
e l'int
non può essere modificato tramite il puntatore.int a, b; const int* const p = &b; /* OK as initialisation, no assignment */ p = &a; /* Compiler Error */ *p = 100; /* Compiler Error */
Puntatore al puntatore
Puntatore a un puntatore a un
int
Questo codice assegna l'indirizzo di
p1
al doppio puntatorep
(che punta quindi aint* p1
(che punta aint
)).Quindi cambia
p1
in modo che punti aint a
. Quindi cambia il valore di a per essere 100.void f1(void) { int a, b; int *p1; int **p; p1 = &b; /* OK */ p = &p1; /* OK */ *p = &a; /* OK */ **p = 100; /* OK */ }
Puntatore per puntare a un
const int
void f2(void) { int b; const int *p1; const int **p; p = &p1; /* OK */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
Puntatore a
const
puntatore a unint
void f3(void) { int b; int *p1; int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
const
puntatore a puntatore aint
void f4(void) { int b; int *p1; int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* OK */ }
Puntatore a
const
puntatore aconst int
void f5(void) { int b; const int *p1; const int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
const
puntatore a puntatore aconst int
void f6(void) { int b; const int *p1; const int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
const
puntatore aconst
puntatore aint
void f7(void) { int b; int *p1; int * const * const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
Same Asterisk, Different Meanings
Premessa
La cosa più confusa che circonda la sintassi del puntatore in C e C ++ è che ci sono in realtà due significati diversi che si applicano quando il simbolo del puntatore, l'asterisco ( *
), viene utilizzato con una variabile.
Esempio
In primo luogo, si usa *
per dichiarare una variabile puntatore.
int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */
Quando non stai dichiarando (o moltiplicando), *
è usato per dereferenziare una variabile puntatore:
*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */
Quando vuoi una variabile puntatore esistente per contenere l'indirizzo di un'altra variabile, non usi *
, ma fallo in questo modo:
p = &another_variable;
Una confusione comune tra i neofiti di programmazione C si verifica quando dichiarano e inizializzano una variabile puntatore allo stesso tempo.
int *p = &i;
Poiché int i = 5;
e int i; i = 5;
dare lo stesso risultato, alcuni potrebbero pensare int *p = &i;
e int *p; *p = &i;
dare lo stesso risultato anche Il fatto è, no, int *p; *p = &i;
tenterà di deferire un puntatore non inizializzato che risulterà in UB. Non usare mai *
quando non stai dichiarando né dereferenziare un puntatore.
Conclusione
L'asterisco ( *
) ha due significati distinti all'interno di C in relazione ai puntatori, a seconda di dove viene utilizzato. Se utilizzato all'interno di una dichiarazione di variabile , il valore sul lato destro del lato uguale deve essere un valore puntatore a un indirizzo in memoria. Quando viene utilizzato con una variabile già dichiarata , l'asterisco dedurrà il riferimento al valore del puntatore, lo seguirà nel punto appuntito in memoria e consentirà di assegnare o recuperare il valore memorizzato.
Porta via
È importante tenere a mente le tue P e Q, per così dire, quando si tratta di puntatori. Fai attenzione quando usi l'asterisco e cosa significa quando lo usi lì. Affrontare questo piccolo dettaglio potrebbe portare a comportamenti buggy e / o indefiniti che in realtà non si vuole avere a che fare.
Puntatore al puntatore
In C, un puntatore può fare riferimento a un altro puntatore.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &pA;
int*** pppA = &ppA;
printf("%d", ***pppA); /* prints 42 */
return EXIT_SUCCESS;
}
Ma, riferimento-e-riferimento direttamente non è permesso.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &&A; /* Compilation error here! */
int*** pppA = &&&A; /* Compilation error here! */
...
introduzione
Un puntatore viene dichiarato molto simile a qualsiasi altra variabile, tranne che un asterisco ( *
) è posto tra il tipo e il nome della variabile per indicare che si tratta di un puntatore.
int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */
Per dichiarare due variabili puntatore dello stesso tipo, nella stessa dichiarazione, utilizzare il simbolo asterisco prima di ogni identificatore. Per esempio,
int *iptr1, *iptr2;
int *iptr3, iptr4; /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */
L'operatore di riferimento o di riferimento indicato da una e commerciale ( &
) fornisce l'indirizzo di una determinata variabile che può essere inserito in un puntatore di tipo appropriato.
int value = 1;
pointer = &value;
L'operatore di riferimento o dereferenza contrassegnato da un asterisco ( *
) ottiene il contenuto di un oggetto puntato da un puntatore.
printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */
Se il puntatore punta a una struttura oa un tipo di unione, puoi dereferenziarlo e accedere direttamente ai suoi membri usando l'operatore ->
:
SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */
In C, un puntatore è un tipo di valore distinto che può essere riassegnato e altrimenti viene trattato come una variabile a sé stante. Ad esempio il seguente esempio stampa il valore del puntatore (variabile) stesso.
printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */
Poiché un puntatore è una variabile mutabile, è possibile che non punti a un oggetto valido, sia impostandolo su null
pointer = 0; /* or alternatively */
pointer = NULL;
o semplicemente contenendo un pattern di bit arbitrario che non è un indirizzo valido. Quest'ultima è una situazione molto brutta, perché non può essere testata prima che il puntatore venga sottoposto a dereferenziazione, c'è solo un test per il caso in cui un puntatore è nullo:
if (!pointer) exit(EXIT_FAILURE);
Un puntatore può essere sottoposto a dereferenziazione solo se punta a un oggetto valido , altrimenti il comportamento non è definito. Molte implementazioni moderne possono aiutarti a sollevare qualche tipo di errore come un errore di segmentazione e terminare l'esecuzione, ma altri potrebbero semplicemente lasciare il tuo programma in uno stato non valido.
Il valore restituito dall'operatore di dereferenziazione è un alias mutabile della variabile originale, quindi può essere modificato, modificando la variabile originale.
*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */
I puntatori sono anche riassegnabili. Ciò significa che un puntatore che punta a un oggetto può essere successivamente utilizzato per puntare a un altro oggetto dello stesso tipo.
int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */
Come qualsiasi altra variabile, i puntatori hanno un tipo specifico. Ad esempio, non è possibile assegnare l'indirizzo di un short int
a un puntatore a un long int
. Tale comportamento è indicato come punire tipo ed è vietato in C, anche se ci sono alcune eccezioni.
Sebbene il puntatore debba essere di un tipo specifico, la memoria allocata per ciascun tipo di puntatore è uguale alla memoria utilizzata dall'ambiente per memorizzare gli indirizzi, piuttosto che la dimensione del tipo puntato.
#include <stdio.h>
int main(void) {
printf("Size of int pointer: %zu\n", sizeof (int*)); /* size 4 bytes */
printf("Size of int variable: %zu\n", sizeof (int)); /* size 4 bytes */
printf("Size of char pointer: %zu\n", sizeof (char*)); /* size 4 bytes */
printf("Size of char variable: %zu\n", sizeof (char)); /* size 1 bytes */
printf("Size of short pointer: %zu\n", sizeof (short*)); /* size 4 bytes */
printf("Size of short variable: %zu\n", sizeof (short)); /* size 2 bytes */
return 0;
}
(NB: se si utilizza Microsoft Visual Studio, che non supporta gli standard C99 o C11, è necessario utilizzare %Iu
1 anziché %zu
nell'esempio precedente.)
Si noti che i risultati sopra riportati possono variare da ambiente a ambiente in numeri, ma tutti gli ambienti mostrerebbero uguali dimensioni per diversi tipi di puntatore.
Estratto basato sulle informazioni fornite dall'Università di Cardiff C Pointers Introduzione
Puntatori e matrici
Puntatori e array sono intimamente connessi in C. Le matrici in C si trovano sempre in posizioni contigue nella memoria. L'aritmetica del puntatore viene sempre ridimensionata in base alla dimensione dell'oggetto puntato. Quindi, se abbiamo una matrice di tre doppi e un puntatore alla base, *ptr
riferisce al primo doppio, *(ptr + 1)
al secondo, *(ptr + 2)
al terzo. Una notazione più conveniente è usare la notazione array []
.
double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;
/* prints x 0.0, y 1.0 z 2.0 */
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);
Quindi essenzialmente ptr e il nome dell'array sono intercambiabili. Questa regola significa anche che un array decade a un puntatore quando viene passato a una subroutine.
double point[3] = {0.0, 1.0, 2.0};
printf("length of point is %s\n", length(point));
/* get the distance of a 3D point from the origin */
double length(double *pt)
{
return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}
Un puntatore può puntare a qualsiasi elemento in una matrice o all'elemento oltre l'ultimo elemento. È tuttavia un errore impostare un puntatore su qualsiasi altro valore, incluso l'elemento prima dell'array. (Il motivo è che su architetture segmentate l'indirizzo prima che il primo elemento possa attraversare un limite di segmento, il compilatore assicura che ciò non avvenga per l'ultimo elemento più uno).
Nota 1: informazioni sul formato Microsoft possono essere trovate tramite printf()
e sintassi delle specifiche del formato .
Comportamento polimorfico con puntatori void
La qsort()
libreria standard qsort()
è un buon esempio di come si possano usare i puntatori void per far funzionare una singola funzione su una grande varietà di tipi diversi.
void qsort (
void *base, /* Array to be sorted */
size_t num, /* Number of elements in array */
size_t size, /* Size in bytes of each element */
int (*compar)(const void *, const void *)); /* Comparison function for two elements */
La matrice da ordinare viene passata come un puntatore vuoto, quindi è possibile utilizzare una matrice di qualsiasi tipo di elemento. I prossimi due argomenti dicono a qsort()
quanti elementi deve aspettarsi nell'array e quanto è grande, in byte, ogni elemento.
L'ultimo argomento è un puntatore a funzione di una funzione di confronto che a sua volta prende due puntatori void. Facendo in modo che il chiamante fornisca questa funzione, qsort()
può effettivamente ordinare elementi di qualsiasi tipo.
Ecco un esempio di tale funzione di confronto, per confrontare i float. Si noti che qualsiasi funzione di confronto passata a qsort()
deve avere questa firma di tipo. Il modo in cui è reso polimorfico è colando gli argomenti del puntatore del vuoto ai puntatori del tipo di elemento che vogliamo confrontare.
int compare_floats(const void *a, const void *b)
{
float fa = *((float *)a);
float fb = *((float *)b);
if (fa < fb)
return -1;
if (fa > fb)
return 1;
return 0;
}
Dato che sappiamo che qsort userà questa funzione per confrontare i float, convertiamo gli argomenti del puntatore void indietro in virgola mobile prima di dereferenziarli.
Ora, l'uso della funzione polimorfica qsort su un array "array" con lunghezza "len" è molto semplice:
qsort(array, len, sizeof(array[0]), compare_floats);