Puntatori come argomenti di funzione in C

Se dovessi avere questo codice, ad esempio:

int num = 5; int *ptr = # 

Qual è la differenza tra le seguenti due funzioni?

 void func(int **foo); void func(int *foo); 

Dove chiamo la funzione:

 func(&ptr); 

Mi rendo conto che il primo dei due assume come parametro un puntatore a un puntatore, mentre il secondo accetta solo un puntatore.

Se passo in func(&ptr) , sto effettivamente passando un puntatore. Che differenza fa che il puntatore punti ad un altro puntatore?

Credo che quest’ultimo fornirà un avviso di incompatibilità, ma sembra che i dettagli non contano finché sai cosa stai facendo. Sembra che forse per motivi di leggibilità e comprensione il primo sia un’opzione migliore (puntatore a 2 stelle), ma da un punto di vista logico, qual è la differenza?

Una ragionevole regola empirica è che non è ansible cambiare esattamente la cosa esatta che viene passata è un modo tale che il chiamante vede la modifica. Passare puntatori è la soluzione.

Passa per valore: void fcn(int foo)

Quando si passa per valore, si ottiene una copia del valore. Se si modifica il valore nella propria funzione, il chiamante continua a vedere il valore originale indipendentemente dalle modifiche.

Passa per puntatore al valore: void fcn(int* foo)

Passare da un puntatore ti dà una copia del puntatore – punta alla stessa posizione di memoria dell’originale. Questa posizione di memoria è dove l’originale è memorizzato. Questo ti permette di cambiare il valore puntato. Tuttavia, non è ansible modificare il puntatore effettivo sui dati poiché è stata ricevuta solo una copia del puntatore.

Passa il puntatore al puntatore al valore: void fcn(int** foo)

Si ottiene quanto sopra passando un puntatore a un puntatore a un valore. Come sopra, è ansible modificare il valore in modo che il chiamante vedrà la modifica perché è la stessa posizione di memoria utilizzata dal codice del chiamante. Per lo stesso motivo, puoi cambiare il puntatore al valore. Questo ti permette di fare cose come allocare memoria all’interno della funzione e restituirla; &arg2 = calloc(len); . Non è ancora ansible cambiare il puntatore al puntatore, poiché questa è la cosa che si riceve una copia di.

La differenza viene semplicemente detta nelle operazioni con cui il processore gestirà il codice. il valore stesso è solo un indirizzo in entrambi i casi, questo è vero. Ma poiché l’indirizzo viene deferito, è importante per il processore e così anche per il compilatore, sapere dopo il dereferenziamento, con che cosa gestirà.

Se dovessi avere questo codice, ad esempio:

 int num = 5; int *ptr = # 

Qual è la differenza tra le seguenti due funzioni ?:

 void func(int **foo); void func(int *foo); 

Il primo vuole un puntatore a un puntatore a un int, il secondo vuole un puntatore che punta direttamente a un int.

Dove chiamo la funzione:

 func(&ptr); 

Poichè ptr è un puntatore a un int, &ptr è un indirizzo, compatibile con un int ** .

La funzione che prende un int * farà qualcosa di diverso come con int ** . Il risultato della conversazione sarà completamente diverso, portando a un comportamento indefinito, forse causando un arresto anomalo.

Se passo in func (& ptr) sto effettivamente passando un puntatore. Che differenza fa che il puntatore punti ad un altro puntatore?

  +++++++++++++++++++ adr1 (ptr): + adr2 + +++++++++++++++++++ +++++++++++++++++++ adr2 (num): + 42 + +++++++++++++++++++ 

Ad adr2 , abbiamo un valore int, 42.

Ad adr1 , abbiamo l’indirizzo adr2 , con le dimensioni di un puntatore.

&ptr ci dà adr1, ptr , contiene il valore di &num , che è adr2.

Se uso adr1 come int * , adr2 verrà erroneamente trattato come un numero intero, portando a un numero (possibilmente piuttosto grande).

Se uso adr2 come int ** , il primo dereferenziamento porta a 42, che verrà erroneamente interpretato come un indirizzo e potrebbe causare il crash del programma.

È più che l’ottica avere una differenza tra int * e int ** .

Credo che quest’ultimo darà un avvertimento di incompatibilità,

… che ha un significato …

ma sembra che i dettagli non contano finché sai cosa stai facendo.

Fai?

Sembra che forse per motivi di leggibilità e comprensione il primo sia un’opzione migliore (puntatore a 2 stelle), ma da un punto di vista logico, qual è la differenza?

Dipende da cosa fa la funzione con il puntatore.

Ci sono due principali differenze pratiche:

  1. Passare un puntatore a un puntatore consente alla funzione di modificare il contenuto di quel puntatore in modo che il chiamante possa vedere. Un classico esempio è il secondo argomento di strtol() . Dopo una chiamata a strtol() , il contenuto di quel puntatore dovrebbe puntare al primo carattere nella stringa che non è stato analizzato per calcolare il valore long . Se hai appena passato il puntatore a strtol() , le eventuali modifiche apportate sarebbero locali e sarebbe imansible comunicare al chiamante quale fosse la posizione. Passando l’indirizzo di quel puntatore, strtol() può modificarlo in modo che il chiamante possa vedere. È come passare l’indirizzo di qualsiasi altra variabile.

  2. Più fondamentalmente, il compilatore deve conoscere il tipo a cui è indirizzato per dereferenziare. Ad esempio, quando si dereferenzia un double * , il compilatore interpreterà (in un’implementazione in cui il double consuma 8 byte) gli 8 byte che iniziano nella posizione di memoria come valore del doppio. Ma, in un’implementazione a 32 bit, quando si dereferenzia un double ** , il compilatore interpreterà i 4 byte che iniziano in quella posizione come l’indirizzo di un altro doppio. Quando si denota un puntatore, il tipo puntato è l’unica informazione che il compilatore ha su come interpretare i dati a quell’indirizzo, quindi conoscere il tipo esatto è critico, e questo è il motivo per cui sarebbe un errore pensare “sono tutti solo puntatori, quindi qual è la differenza “?

Generalmente la differenza indica che la funzione verrà assegnata al puntatore e che questa assegnazione non dovrebbe essere solo locale alla funzione. Ad esempio (e tieni presente che questi esempi hanno lo scopo di esaminare la natura delle funzioni foo e non complete, non più di quanto il codice nel tuo post originale dovrebbe essere un vero codice di lavoro):

 void func1 (int *foo) { foo = malloc (sizeof (int)); } int a = 5; func1 (&a); 

È simile a

 void func2 (int foo) { foo = 12; } int b = 5; func2 (b); 

Nel senso che foo può essere uguale a 12 in func2 (), ma quando func2 () ritorna, b sarà comunque uguale a 5. In func1 (), foo punta a un nuovo int, ma a è ancora a quando func1 () restituisce.

E se volessimo cambiare il valore di b ? WRT b , un normale int:

 void func3 (int *foo) { *foo = 12; } int b = 5; func2 (&b); 

Funzionerà – notiamo che avevamo bisogno di un puntatore a un int. Per cambiare il valore in un puntatore (cioè l’indirizzo dell’int a cui punta, e non solo il valore nell’int che punta a):

 void func4 (int **foo) { *foo = malloc (sizeof (int)); } int *a; foo (&a); 

‘a’ ora punta alla memoria restituita da malloc in func4 (). L’indirizzo &a è l’indirizzo di a , un puntatore a un int . Un puntatore int contiene l’indirizzo di un int. func4() prende l’indirizzo di un puntatore int in modo che possa inserire l’indirizzo di un int in questo indirizzo, proprio come func3 () prende l’indirizzo di un int in modo che possa inserire un nuovo valore int in esso.

Ecco come vengono usati i diversi stili di argomento.

È passato un po ‘di tempo da quando è stato chiesto questo, ma ecco il mio punto di vista su questo. Ora sto cercando di imparare C e i puntatori sono infinitamente confusi … Quindi mi sto prendendo questo tempo per chiarire i puntatori sui puntatori, almeno per me. Ecco come ci penso.
Ho preso un esempio da qui :

 #include  #include  int allocstr(int len, char **retptr) { char *p = malloc(len + 1); /* +1 for \0 */ if(p == NULL) return 0; *retptr = p; return 1; } int main() { char *string = "Hello, world!"; char *copystr; if(allocstr(strlen(string), &copystr)) strcpy(copystr, string); else fprintf(stderr, "out of memory\n"); return 0; } 

Mi chiedevo perché allocstr avesse bisogno di un doppio puntatore. Se è un puntatore significa che puoi passarlo e verrà cambiato dopo il ritorno …
Se fai questo esempio, funziona bene. Ma se si modifica l’ allocstr in modo da avere solo * pointer invece di ** pointer (e copystr invece di & copystr in main) si ottiene un errore di segmentazione . Perché? Inserisco alcuni printfs nel codice e funziona bene fino alla riga con strcpy . Quindi suppongo che non abbia allocato memoria per copystr . Di nuovo, perché?
Torniamo a cosa significa passare il puntatore. Significa che passi la posizione di memoria e puoi scrivere direttamente lì il valore che desideri. È ansible modificare il valore perché si ha accesso alla posizione di memoria del proprio valore.
Allo stesso modo, quando si passa un puntatore a un puntatore, si passa la posizione di memoria del puntatore – in altre parole, la posizione di memoria della posizione di memoria. E ora (puntatore al puntatore) è ansible modificare la posizione della memoria in quanto è ansible modificare il valore quando si utilizzava solo un puntatore.
Il motivo per cui il codice funziona è che si passa l’indirizzo di una posizione di memoria. La funzione allocstr cambia la dimensione di quella posizione di memoria in modo che possa contenere “Hello world!” e restituisce un puntatore a quella posizione di memoria.
È proprio come passare un puntatore, ma invece di un valore abbiamo una posizione di memoria.