Come funzionano i puntatori ai puntatori in C?

Come funzionano i puntatori ai puntatori in C? Quando li useresti?

Assumiamo un computer a 8 bit con indirizzi a 8 bit (e quindi solo 256 byte di memoria). Questo fa parte di quella memoria (i numeri in alto sono gli indirizzi):

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | | 58 | | | 63 | | 55 | | | h | e | l | l | o | \0 | | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ 

Quello che puoi vedere qui è che all’indirizzo 63 inizia la stringa “ciao”. Quindi, in questo caso, se questa è l’unica occorrenza di “ciao” nella memoria, allora

 const char *c = "hello"; 

… definisce c come puntatore alla stringa (sola lettura) “ciao”, e quindi contiene il valore 63. c deve essere memorizzato da qualche parte: nell’esempio sopra alla posizione 58. Ovviamente non possiamo solo puntare ai personaggi, ma anche ad altri puntatori. Per esempio:

 const char **cp = &c; 

Ora cp punta a c , cioè contiene l’indirizzo di c (che è 58). Possiamo andare anche oltre. Prendere in considerazione:

 const char ***cpp = &cp; 

Ora cpp memorizza l’indirizzo di cp . Quindi ha valore 55 (basato sull’esempio sopra), e lo hai indovinato: è esso stesso memorizzato all’indirizzo 60.


Sul perché uno utilizza i puntatori ai puntatori:

  • Il nome di una matrice di solito produce l’indirizzo del suo primo elemento. Quindi se la matrice contiene elementi di tipo t , un riferimento all’array ha tipo t * . Consideriamo ora un array di matrici di tipo t : naturalmente un riferimento a questo array 2D avrà type (t *)* = t ** , ed è quindi un puntatore a un puntatore.
  • Anche se una serie di stringhe suona unidimensionale, in realtà è bidimensionale, poiché le stringhe sono matrici di caratteri. Quindi: char ** .
  • Una funzione f dovrà accettare un argomento di tipo t ** se deve modificare una variabile di tipo t * .
  • Molte altre ragioni che sono troppo numerose per essere elencate qui.

Come funzionano i puntatori ai puntatori in C?

Prima un puntatore è una variabile, come qualsiasi altra variabile, ma che contiene l’indirizzo di una variabile.

Un puntatore a un puntatore è una variabile, come qualsiasi altra variabile, ma che contiene l’indirizzo di una variabile. Quella variabile sembra essere solo un puntatore.

Quando li useresti?

È ansible utilizzarli quando è necessario restituire un puntatore a una memoria nell’heap, ma non utilizzare il valore restituito.

Esempio:

 int getValueOf5(int *p) { *p = 5; return 1;//success } int get1024HeapMemory(int **p) { *p = malloc(1024); if(*p == 0) return -1;//error else return 0;//success } 

E tu lo chiami così:

 int x; getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in //At this point x holds 5 int *p; get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in //At this point p holds a memory address where 1024 bytes of memory is allocated on the heap 

Ci sono anche altri usi, come l’argomento main () di ogni programma C ha un puntatore a un puntatore per argv, in cui ogni elemento contiene una matrice di caratteri che sono le opzioni della riga di comando. Bisogna fare attenzione però quando si usano puntatori di puntatori per puntare a array bidimensionali, è preferibile utilizzare un puntatore a un array bidimensionale.

Perché è pericoloso?

 void test() { double **a; int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*) double matrix[ROWS][COLUMNS]; int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double) } 

Ecco un esempio di puntatore a un array bidimensionale eseguito correttamente:

 int (*myPointerTo2DimArray)[ROWS][COLUMNS] 

Non è ansible utilizzare un puntatore a un array bidimensionale se si desidera supportare un numero variabile di elementi per ROWS e COLUMNS. Ma quando conosci prima mano useresti un array bidimensionale.

Mi piace questo esempio di codice “mondo reale” del puntatore all’uso del puntatore, in Git 2.0, commit 7b1004b :

Linus una volta disse:

In realtà vorrei che più persone capissero il vero e proprio tipo di codifica di basso livello. Cose non grandi e complesse come la ricerca del nome senza chiave, ma semplicemente buon uso di puntatori-puntatori ecc.
Ad esempio, ho visto troppe persone che cancellano una voce di elenco collegata singolarmente tenendo traccia della voce “prev”, e quindi per eliminare la voce, facendo qualcosa come

 if (prev) prev->next = entry->next; else list_head = entry->next; 

e ogni volta che vedo un codice del genere, vado semplicemente “Questa persona non capisce i puntatori”. Ed è purtroppo abbastanza comune.

Le persone che comprendono i puntatori utilizzano semplicemente un ” puntatore al puntatore di voce ” e inizializzano quello con l’indirizzo di list_head. E poi mentre attraversano la lista, possono rimuovere la voce senza usare condizionali, semplicemente facendo una

 *pp = entry->next 

http://i.stack.imgur.com/bpfxT.gif

Applicare questa semplificazione ci fa perdere 7 righe da questa funzione anche aggiungendo 2 righe di commento.

 - struct combine_diff_path *p, *pprev, *ptmp; + struct combine_diff_path *p, **tail = &curr; 

Chris sottolinea nei commenti al video del 2016 ” Linus Torvalds’s Double Pointer Problem ” di Philip Buuck .


kumar sottolinea nei commenti il post del blog ” Linus on Understanding Pointers “, dove Grisha Trubetskoy spiega:

Immagina di avere un elenco collegato definito come:

 typedef struct list_entry { int val; struct list_entry *next; } list_entry; 

È necessario iterare su di esso dall’inizio alla fine e rimuovere un elemento specifico il cui valore è uguale al valore di to_remove.
Il modo più ovvio per farlo sarebbe:

 list_entry *entry = head; /* assuming head exists and is the first entry of the list */ list_entry *prev = NULL; while (entry) { /* line 4 */ if (entry->val == to_remove) /* this is the one to remove ; line 5 */ if (prev) prev->next = entry->next; /* remove the entry ; line 7 */ else head = entry->next; /* special case - first entry ; line 9 */ /* move on to the next entry */ prev = entry; entry = entry->next; } 

Quello che stiamo facendo sopra è:

  • iterando sulla lista fino a quando la voce è NULL , il che significa che abbiamo raggiunto la fine dell’elenco (riga 4).
  • Quando incontriamo una voce che vogliamo rimuovere (riga 5),
    • assegniamo il valore dell’attuale puntatore successivo a quello precedente,
    • eliminando così l’elemento corrente (riga 7).

C’è un caso speciale sopra – all’inizio dell’iterazione non c’è una voce precedente ( prev è NULL ), e quindi per rimuovere la prima voce nell’elenco devi modificare head itself (riga 9).

Quello che Linus stava dicendo è che il codice precedente potrebbe essere semplificato rendendo l’elemento precedente un puntatore a un puntatore anziché solo un puntatore .
Il codice quindi assomiglia a questo:

 list_entry **pp = &head; /* pointer to a pointer */ list_entry *entry = head; while (entry) { if (entry->val == to_remove) *pp = entry->next; pp = &entry->next; entry = entry->next; } 

Il codice sopra riportato è molto simile alla variante precedente, ma notiamo come non è più necessario osservare il caso speciale del primo elemento della lista, poiché pp non è NULL all’inizio. Semplice e intelligente.

Inoltre, qualcuno in quel thread ha commentato che il motivo per cui questo è migliore è perché *pp = entry->next è atomic. Di certo NON è atomico .
L’espressione sopra contiene due operatori di dereferenziazione ( * e -> ) e un compito, e nessuna di queste tre cose è atomica.
Questo è un malinteso comune, ma ahimè praticamente nulla in C dovrebbe mai essere considerato atomico (inclusi gli operatori ++ e -- )!

Quando abbiamo coperto i puntatori di un corso di programmazione all’università, ci hanno dato due suggerimenti su come iniziare a conoscerli. Il primo era vedere Pointer Fun With Binky . Il secondo è stato quello di pensare al passaggio degli occhi di Haddocks da Through the Looking-Glass di Lewis Carroll

“Sei triste,” disse il Cavaliere in tono ansioso: “Lascia che ti canti una canzone per consolarti.”

“È molto lungo?” Chiese Alice, perché quel giorno aveva ascoltato una buona dose di poesia.

“È lungo”, disse il Cavaliere, “ma è molto, molto bello. Tutti quelli che mi sentono cantare – o portano le lacrime agli occhi, oppure – ”

“Altrimenti cosa?” Disse Alice, perché il Cavaliere aveva fatto una pausa improvvisa.

“Altrimenti no, lo sai. Il nome della canzone è chiamato ‘Haddocks’ Eyes. ‘”

“Oh, questo è il nome della canzone, vero?” Disse Alice, cercando di sentirsi interessata.

“No, non capisci” disse il Cavaliere, sembrando un po ‘contrariato. “È così che si chiama il nome. Il nome è davvero “The Aged Maned Man”. ”

“Allora avrei dovuto dire ‘è quello che si chiama la canzone’?” Si corresse Alice.

“No, non dovresti: è tutta un’altra cosa! La canzone si chiama ‘Ways And Means’: ma è solo quello che si chiama, lo sai! ”

“Bene, qual è la canzone, allora?” Disse Alice, che era ormai completamente disorientata.

“Stavo arrivando a quello”, disse il Cavaliere. “La canzone è davvero ‘A-sitting On A Gate’: e la melodia è la mia invenzione.”

Si consiglia di leggere questo: Puntatori a puntatori

Spero che questo aiuti a chiarire alcuni dubbi di base.

Quando è richiesto un riferimento a un puntatore. Ad esempio, quando si desidera modificare il valore (indirizzo puntato a) di una variabile puntatore dichiarata nell’ambito di una funzione di chiamata all’interno di una funzione chiamata.

Se si passa un singolo puntatore come argomento, si modificheranno le copie locali del puntatore, non il puntatore originale nell’ambito di chiamata. Con un puntatore a un puntatore, si modifica il secondo.

Un puntatore a un puntatore viene anche chiamato handle . Un utilizzo è spesso quando un object può essere spostato in memoria o rimosso. Uno è spesso responsabile di bloccare e sbloccare l’utilizzo dell’object in modo che non venga spostato durante l’accesso.

Viene spesso utilizzato in un ambiente con memoria limitata, ad esempio il Palm OS.

collegamento computer.howstuffworks.com >>

http://www.flippinbits.com Link >>

Hai una variabile che contiene un indirizzo di qualcosa. Questo è un puntatore.

Quindi hai un’altra variabile che contiene l’indirizzo della prima variabile. Questo è un puntatore al puntatore.

è un puntatore al valore dell’indirizzo del puntatore. (è terribile, lo so)

in pratica, ti permette di passare un puntatore al valore dell’indirizzo di un altro puntatore, così puoi modificare dove un altro puntatore punta da una funzione secondaria, come:

 void changeptr(int** pp) { *pp=&someval; } 

Considera la figura e il programma sottostanti per capire meglio questo concetto .

Doppio diagramma di puntatori

Come per la figura, ptr1 è un singolo puntatore che sta avendo l’indirizzo della variabile num .

 ptr1 = # 

Allo stesso modo ptr2 è un puntatore al puntatore (doppio puntatore) che sta avendo l’indirizzo del puntatore ptr1 .

 ptr2 = &ptr1; 

Un puntatore che punta a un altro puntatore è noto come doppio puntatore. In questo esempio ptr2 è un doppio puntatore.

Valori dal diagramma sopra:

 Address of variable num has : 1000 Address of Pointer ptr1 is: 2000 Address of Pointer ptr2 is: 3000 

Esempio:

 #include  int main () { int num = 10; int *ptr1; int **ptr2; // Take the address of var ptr1 = # // Take the address of ptr1 using address of operator & ptr2 = &ptr1; // Print the value printf("Value of num = %d\n", num ); printf("Value available at *ptr1 = %d\n", *ptr1 ); printf("Value available at **ptr2 = %d\n", **ptr2); } 

Produzione:

 Value of num = 10 Value available at *ptr1 = 10 Value available at **ptr2 = 10 

Un puntatore al puntatore è, beh, un puntatore al puntatore.

Un esempio significativo di someType ** è un array bidimensionale: hai un array, pieno di puntatori ad altri array, quindi quando scrivi

DPointer [5] [6]

si accede all’array che contiene puntatori ad altri array nella sua quinta posizione, ottiene il puntatore (lascia fpointer il suo nome) e quindi accede al sesto elemento dell’array referenziato a quell’array (quindi, fpointer [6]).

Come funziona: è una variabile in grado di memorizzare un altro puntatore.

Quando li useresti: molti ne usano uno se la tua funzione vuole build un array e restituirlo al chiamante.

 //returns the array of roll nos {11, 12} through paramater // return value is total number of students int fun( int **i ) { int *j; *i = (int*)malloc ( 2*sizeof(int) ); **i = 11; // eg, newly allocated memory 0x2000 store 11 j = *i; j++; *j = 12; ; // eg, newly allocated memory 0x2004 store 12 return 2; } int main() { int *i; int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers. for ( int j=0; j 

Ho creato un video di 5 minuti che spiega come funzionano i puntatori:

https://www.youtube.com/watch?v=3X-ray3tDjQ

secchi puntatore

Ci sono così tante delle spiegazioni utili, ma non ho trovato solo una breve descrizione, quindi ..

Fondamentalmente il puntatore è l’indirizzo della variabile. Breve codice riassuntivo:

  int a, *p_a;//declaration of normal variable and int pointer variable a = 56; //simply assign value p_a = &a; //save address of "a" to pointer variable *p_a = 15; //override the value of the variable //print 0xfoo and 15 //- first is address, 2nd is value stored at this address (that is called dereference) printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a); 

Anche informazioni utili possono essere trovate nel tema Cosa significa riferimento e dereferenziazione

E non ne sono così sicuro, quando possono essere utili i puntatori, ma in comune è necessario usarli quando si eseguono allocazioni di memoria manuali / dinamiche: malloc, calloc, ecc.

Quindi spero che aiuti anche a chiarire il problema 🙂