2010-03-26 20 views
7

Ho notato che è un linguaggio comune in C accettare un puntatore un-malloc come secondo argomento invece di restituire un puntatore. Esempio:puntatore come secondo argomento invece di restituire il puntatore?

/*function prototype*/  
void create_node(node_t* new_node, void* _val, int _type); 

/* implementation */ 
node_t* n; 
create_node(n, &someint, INT) 

Invece di

/* function prototype */ 
node_t* create_node(void* _val, int _type) 

/* implementation */ 
node_t* n = create_node(&someint, INT) 

Quali sono i vantaggi e/o svantaggi di entrambi gli approcci?

Grazie!

MODIFICA Grazie a tutti per le vostre risposte. Le motivazioni per la scelta 1 mi sono molto chiare ora (e devo sottolineare che l'argomento del puntatore per la scelta 1 dovrebbe essere malloc'd contrariamente a quello che pensavo inizialmente).

risposta

15

Accettazione un puntatore (che il chiamante è responsabile malloc'ing o meno) di memoria da riempire, offre seri vantaggi in termini di flessibilità sopra ritorno un puntatore (necessariamente malloc'ed). In particolare, se il chiamante sa che è necessario utilizzare qualsiasi cosa restituita solo all'interno di una determinata funzione, può passare all'indirizzo di una struttura o matrice allocato allo stack; se sa che non ha bisogno di rientranza, può passare all'indirizzo di una struct o array static - in entrambi i casi, viene salvata una coppia malloc/free, e tali risparmi vengono montati! -)

+0

Questo. La capacità di fare 'struct thing t; init_thing (&t); 'è bello – dmckee

+0

Se volessi scrivere una funzione per copiare una lista collegata, avrei un prototipo di funzione come' void copy_list (node_t * new_head, node_t * original_head) '? Penso che avrei dovuto malloc a nuovo nodo per ognuno nella lista originale e aggiungilo alla 'fine' di new_head (copia val e type ma dagli un nuovo valore di puntatore 'node_t * next'). E 'questo il modo migliore per farlo? – Tyler

+1

@ Tyler, considera 'copy_list (node_t ** new_head, const node_t * original_head)' come una firma preferibile per quella funzione (anche se questo caso particolare _returning_ a 'node_t *' è sicuramente anche una ragionevole possibilità.) –

0

personalmente mi piace restituire i dati utilizzando i parametri refernce o pointer e utilizzare la funzione return per restituire i codici di errore.

5

Che non ha molto senso I puntatori in C vengono passati per valore, proprio come gli altri oggetti, la differenza sta nel valore. Con i puntatori, il valore è l'indirizzo di memoria, che viene passato alla funzione. Tuttavia, stai ancora duplicando il valore, quindi quando esegui il malloc, cambierai il valore del puntatore all'interno della tua funzione, non quello all'esterno.

void create_node(node_t* new_node, void* _val, int _type) { 
    new_node = malloc(sizeof(node_t) * SIZE); 
    // `new_node` points to the new location, but `n` doesn't. 
    ... 
} 

int main() { 
    ... 
    node_t* n = NULL; 
    create_node(n, &someint, INT); 
    // `n` is still NULL 
    ... 
} 

Ci sono tre modi per evitare questo. Il primo è, come hai detto, il ritorno del nuovo puntatore dalla funzione. Il secondo è quello di prendere un puntatore al puntatore, quindi passando per riferimento:

void create_node(node_t** new_node, void* _val, int _type) { 
    *new_node = malloc(sizeof(node_t) * SIZE); 
    // `*new_node` points to the new location, as does `n`. 
    ... 
} 

int main() { 
    ... 
    node_t* n = NULL; 
    create_node(&n, &someint, INT); 
    // `n` points to the new location 
    ... 
} 

il terzo è semplicemente mallocn all'esterno della funzione chiamata:

int main() { 
    ... 
    node_t* n = malloc(sizeof(node_t) * SIZE); 
    create_node(n, &someint, INT); 
    ... 
} 
+0

Grazie per aver corretto l'errore principale nella mia comprensione. :) – Tyler

+0

Probabilmente dovrei aggiungere che sono d'accordo con Alex Martelli: il terzo metodo è sicuramente il migliore per la maggior parte dei casi. –

2

solito preferiscono ricevere puntatori (proprietà inizializzato) come argomenti di funzione invece di restituire un puntatore a un'area di memoria che è stata malleata all'interno della funzione. Con questo approccio ti stai rendendo esplicito che la responsabilità della gestione della memoria è dalla parte dell'utente.

I puntatori di ritorno di solito portano a perdite di memoria, in quanto è più facile dimenticare di liberare() i puntatori se non li avete malloc().

+0

"È più facile dimenticare di liberare() i puntatori se non li hai malloc()". Non sono sicuro che sia d'accordo, ma ho lavorato per 7 anni su un sistema che non ha liberato memoria all'uscita del processo. Che ti insegna come scrivere codice senza perdite. –

+0

Non intendo come ... "sempre", ma forse un novizio non immagina che il puntatore che la funzione restituisce sia stato ammantato al suo interno. È più una cosa di gusto:] – mgv

+0

@Steve Qual era il sistema e qual è il vantaggio? – Tyler

0

1) Come Samir rilevare il codice è corretto, puntatore viene passato per valore, è necessario **

2) La funzione è essenzialmente un costruttore, quindi ha senso per esso sia allocare la memoria e inizia la struttura dei dati. Il codice Clean C è quasi sempre orientato agli oggetti come quello con costruttori e distruttori.

3) La tua funzione è nulla, ma dovrebbe essere restituita int in modo che possa restituire errori. Ci saranno almeno 2, probabilmente 3 possibili condizioni di errore: malloc può fallire, l'argomento type può essere non valido e possibilmente il valore può essere fuori portata.

1

Di solito non faccio una scelta fissa, la ripongo in modo pulito nella sua libreria e offro il meglio di entrambi i mondi.

void node_init (node_t *n); 

void node_term (node_t *n); 

node_t *node_create() 
{ 
    node_t *n = malloc(sizeof *n); 
    /* boilerplate error handling for malloc returning NULL goes here */ 
    node_init(n); 
    return n; 
} 

void node_destroy (node_t *n) 
{ 
    node_term(n); 
    free(n); 
} 

Per ogni malloc ci dovrebbe essere un libero, quindi per ogni init ci dovrebbe essere un termine e per ogni creazione ci dovrebbe essere un distruggere. Man mano che i tuoi oggetti diventano più complessi, scoprirai che inizi a nidificarli. Alcuni oggetti di livello superiore possono utilizzare un elenco node_t per la gestione interna dei dati. Prima di liberare questo oggetto, l'elenco deve essere prima liberato. _init e _term si preoccupano di questo, nascondendo completamente questo dettaglio di implementazione.

Ci possono essere decisioni su ulteriori dettagli, ad es. destroy può prendere un node_t ** n e impostare * n su NULL dopo averlo liberato.

0

Un problema non discusso in questo articolo è la questione di come si fa riferimento al buffer malloc'ed all'interno della funzione che lo alloca e, presumibilmente, memorizza qualcosa in esso prima che restituisca il controllo al chiamante.

Nel caso in cui mi abbia portato in questa pagina, ho una funzione che passa in un puntatore, che riceve l'indirizzo di una matrice di strutture HOTKEY_STATE. Il prototipo dichiara l'argomento come segue.

HOTKEY_STATE ** plplpHotKeyStates 

Il valore di ritorno, ruintNKeys, è il numero di elementi della matrice, che è determinata dalla routine prima che il buffer è allocato. Piuttosto che usare malloc() direttamente, tuttavia, ho usato calloc, come segue.

*plplpHotKeyStates = (HOTKEY_STATE *) calloc (ruintNKeys , 
               sizeof (HOTKEY_STATE)) ; 

Dopo verificare che plplpHotKeyStates è più nullo, ho definita una variabile puntatore locale, hkHotKeyStates, come segue.

HOTKEY_STATE * hkHotKeyStates = *plplpHotKeyStates ; 

Usando questa variabile, con un intero senza segno per un pedice, il codice popola strutture, utilizzando il semplice operatore punto (.), Come mostrato di seguito.

hkHotKeyStates [ uintCurrKey ].ScanCode = SCANCODE_KEY_ALT ; 

Quando la matrice è completamente popolato, restituisce ruintNKeys, e il chiamante ha tutto il necessario per elaborare la matrice, sia in modo convenzionale, usando l'operatore di riferimento (->), oppure impiegando lo stesso tecnica che ho usato nella funzione per ottenere l'accesso diretto alla matrice.

Problemi correlati