2010-03-26 25 views
7

Observé que es una expresión común en C aceptar un puntero ed no-malloc como segundo argumento en lugar de devolver un puntero. Ejemplo:puntero como segundo argumento en lugar de devolver el puntero?

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

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

En lugar de

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

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

¿Cuáles son las ventajas y/o desventajas de ambos enfoques?

Gracias!

EDIT Gracias a todos por sus respuestas. Las motivaciones para la opción 1 son muy claras para mí ahora (y debo señalar que el argumento del puntero para la opción 1 debería ser malloc'd contrario a lo que originalmente pensé).

Respuesta

15

Aceptando un puntero (que la persona que llama es responsable de malloc'ing o no) a la memoria a ser rellenado, ofrece grandes ventajas en la flexibilidad sobre devolver un puntero (necesariamente malloc'ed). En particular, si la persona que llama sabe que necesita usar lo que se devuelve solo dentro de una determinada función, puede pasar la dirección de una estructura o matriz asignada a la pila; si sabe que no necesita reentrada, puede pasar en la dirección de una estructura o matriz static - en cualquier caso, se guarda un par malloc/libre, y tales ahorros se acumulan! -)

+0

Esto. La capacidad de hacer 'struct thing t; init_thing (&t); 'es bueno. – dmckee

+0

Si quisiera escribir una función para copiar una lista vinculada, ¿tendría un prototipo de función como' void copy_list (node_t * new_head, node_t * original_head) '? Creo que tendría que malloc a nuevo nodo para cada uno en la lista original y agréguelo al 'final' de new_head (copie el val y escriba pero le da un nuevo valor de puntero 'node_t * next'). ¿Es esta la mejor manera de hacerlo? – Tyler

+1

@ Tyler, considere 'copy_list (node_t ** new_head, const node_t * original_head)' como una firma posiblemente preferible para esa función (aunque es este caso peculiar _returning_ a 'node_t *' seguramente también es una posibilidad razonable). –

0

personalmente Me gusta devolver los datos utilizando los parámetros de referencia o puntero, y utilizar la función return para devolver los códigos de error.

5

Eso no tiene mucho sentido. Los punteros en C se transmiten por valor, al igual que otros objetos; la diferencia radica en el valor. Con punteros, el valor es la dirección de memoria, que se pasa a la función. Sin embargo, todavía está duplicando el valor, por lo que cuando malloc, estará cambiando el valor del puntero dentro de su función, no la que está en el exterior.

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 
    ... 
} 

Hay tres formas de evitar esto. El primero es, como usted mencionó, devolver el nuevo puntero desde la función. El segundo es tomar un puntero al puntero, por lo tanto pasándolo por referencia:

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 
    ... 
} 

La tercera es simplemente mallocn fuera de la función de llamada:

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

Gracias por corregir ese gran error en mi comprensión. :) – Tyler

+0

Probablemente debería agregar que estoy de acuerdo con Alex Martelli: el tercer método es definitivamente el mejor para la mayoría de los casos. –

2

por lo general prefieren recibir punteros (propiedad inicializado) como argumentos de función en lugar de devolver un puntero a un área de memoria que ha sido malloc'ed dentro de la función. Con este enfoque, usted hace explícito que la responsabilidad de la administración de la memoria depende del usuario.

La devolución de punteros generalmente provoca fugas de memoria, ya que es más fácil olvidarse de liberar() los punteros si no los ha malloc() 'ed.

+0

"Es más fácil olvidarse de liberar() los punteros si no los ha malloc() 'editado." No estoy seguro de estar de acuerdo con eso, pero trabajé durante 7 años en un sistema que no liberaba memoria en la salida del proceso. Eso te enseña a escribir código sin fugas. –

+0

No me refiero a ... "siempre", pero tal vez un novato no imagine que el puntero que devuelve la función se ha malloced dentro de él. Es más una cuestión de gusto:] – mgv

+0

@Steve ¿Cuál era el sistema y cuál es la ventaja de eso? – Tyler

0

1) Como se señaló Samir el código es incorrecto, el puntero se pasa por valor, es necesario **

2) La función es esencialmente un constructor, así que tiene sentido para que tanto asignar la memoria y init la estructura de datos. El código Clean C casi siempre está orientado a objetos de esa manera con constructores y destructores.

3) Su función es nula, pero debería estar retornando int para que pueda devolver los errores. Habrá al menos 2, probablemente 3 posibles condiciones de error: malloc puede fallar, el argumento de tipo puede ser no válido y posiblemente el valor puede estar fuera del rango.

1

Por lo general, no hago una elección fija, la ubicaré limpiamente en su propia biblioteca y proporcionaré lo mejor de ambos mundos.

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); 
} 

Por cada malloc debe haber una conexión, por lo tanto para cada init debe haber un término y por cada crear debería haber una destruyen. A medida que sus objetos se vuelven más complejos, descubrirá que comienza a anidarlos. Algunos objetos de nivel superior pueden usar una lista node_t para la gestión interna de datos. Antes de liberar este objeto, la lista debe ser liberada primero. _init y _term se preocupan por esto, ocultando completamente este detalle de implementación.

Puede haber decisiones sobre más detalles, p. destroy puede tomar un node_t ** n y establecer * n en NULL después de liberarlo.

0

Un problema no tratado en este artículo es la cuestión de cómo hacer para referirse al búfer malloc'ed dentro de la función que lo asigna y, presumiblemente, almacena algo en él antes de devolver el control a su llamador.

En el caso que me llevó a esta página, tengo una función que pasa en un puntero, que recibe la dirección de una matriz de estructuras HOTKEY_STATE. El prototipo declara el argumento de la siguiente manera.

HOTKEY_STATE ** plplpHotKeyStates 

El valor de retorno, ruintNKeys, es el número de elementos de la matriz, que se determina por la rutina antes se asigna la memoria intermedia. En lugar de utilizar malloc() directamente, sin embargo, utilicé calloc, de la siguiente manera.

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

Después verifico que plplpHotKeyStates ya no es nula, que define una variable de puntero local hkHotKeyStates, de la siguiente manera.

HOTKEY_STATE * hkHotKeyStates = *plplpHotKeyStates ; 

uso de esta variable, con un entero sin signo de un subíndice, el código rellena las estructuras, usando el operador simple miembro (.), Como se muestra a continuación.

hkHotKeyStates [ uintCurrKey ].ScanCode = SCANCODE_KEY_ALT ; 

Cuando la matriz está completamente poblada, devuelve ruintNKeys, y la persona que llama tiene todo lo que necesita para procesar la matriz, ya sea en la forma convencional, utilizando el operador de referencia (->), o mediante el empleo de la misma técnica que utilicé en la función para obtener acceso directo a la matriz.

Cuestiones relacionadas