Sin embargo, hagamos primero un poco de claridad. Cualquier persona (incluyendo yo) que se encuentre con la necesidad de escribir (muy) rápido un pequeño programa para probar algo rápidamente - quizás para una emergencia repentina durante algún mantenimiento crítico de software muy urgente (listo para ayer) - ¿como lo escribe? Por supuesto, con miles de variables automáticas, arrays sobredimensionados ("hay mucha memoria"), tal vez algunas variables estáticas y (horror, horror) variables globales. O sea, en fin, uno de esos programas que incluso mientras lo escribes lo bautizas "temp_halgo.c" porque ya sabes que vas a escribirlo de nuevo tan pronto como sea posible o tendras que borrarlo por la vergüenza (nunca se sabe que alguien lo lee por accidente).
Pero aquí, por lo general, hablamos de estilos y de cosas bien hechas: el C tiene una gestión de memoria potente y flexible (es un lenguaje con punteros!), y escribir en C, fingiendo no saberlo es un error. Pero tenga cuidado: esto no significa que tienes que utilizar siempre punteros y malloc() ("de lo contrario no eres un buen programador"), en realidad es todo lo contrario. Dentro de una función las variables automáticas siempre serán (y con razón) la mayoría, también porque (cuando sea posible y correcto) usar el stack en lugar del heap permite mejorar el rendimiento del programa y, entre otras cosas, hace que sea más fácil de escribir, leer y mantener. La asignación dinámica de memoria es una operación costosa para el sistema operativo, añade (obviamente) complicación en el código y aumenta la posibilidad de errores (levante la mano quien nunca se olvidó de utilizar la free() correspondiente de una malloc()) .
Pero, ¿cuándo y por qué utilizar malloc()? Lo siento por la respuesta un poco "perogrullada", pero yo diría:
1) cuando es indispensable
2) cuando es mejor
Vemos el primer punto de Pedro Grullo:
¿Cuando es indispensable? La asignación dinámica es esencial en al menos dos casos: el primero es lo de las funciones que devuelven una dirección. Vamos a ver un ejemplo sencillo de una función que duplica una cadena (hay uno casi idéntico en K&R):
char *myStrdup( char *src) { // ejecuta un duplicado de src (con sitio para el '\0') char *dest = malloc(strlen(src) + 1); if (dest != NULL) strcpy(dest, src); return dest; }Simple, ¿no? No hace falta decir (pero lo diré de todos modos), que en una función como esta, hay que usar malloc() y no se puede optar por utilizar la dirección de un array automático en el stack, el cual, debido a que está en el stack, se pierde después del return.
El segundo caso con elección forzada es el de las linked list. Veamos un ejemplo muy simplificado (pero perfectamente compilable y comprobable):
// nodo de una single linked list con campo datos typedef struct snode { int data; struct snode *next; } node_t; // alloca un nodo con datos y un puntero al próximo elemento node_t *addNode( node_t *next, int data) { node_t *node = malloc(sizeof *node); node->data = data; node->next = next; return node; } // inserta un nodo en la cabeza de una lista void insertNode( node_t **head, int data) { node_t *node = addNode(*head, data); *head = node; } // main() para test void main() { // lista vacia node_t *head = NULL; // inserta 10 nodos con data=indice loop int i; for (i = 1; i <= 10; i++) insertNode(&head, i); // recorre la lista y enseña los valores while (head) { printf("%d\n", head->data); head = head->next ; } }Para aquellos que nunca la han utilizado, las linked list no son una frivolidad: son una de las herramientas más poderosas de la programación en general (y del C en particular). Los que trabajan intensamente en proyectos grandes y profesional, más pronto o más tarde, acabaran por usarlas: una razón más, por lo tanto, para familiarizarse con la asignación de memoria dinámica.
Pasando al segundo punto de Pedro Grullo:
¿Cuándo es mejor? Sin duda, cada vez que se necesite manejar datos (normalmente arrays de tipos simples o de estructuras), de los cuales no se conoce la dimensión en compile-time: si no usamos malloc() se debería asignar arrays de gran tamaño (para evitar falta de espacio en run-time). Y este detalle nos muestra otro punto: si vamos a manejar grandes cantidades de datos, no podemos confiar demasiado en el stack, donde el espacio no es infinito, incluso si trabajamos con modernos sistemas operativos en máquinas llenas de memoria (y si programamos aplicaciones embebidas con grandes limitaciones Hardware... incluso peor).
Con lo que se ha dicho hasta ahora espero, por lo menos, haber contribuído en hacer un poco de claridad sobre esta cuestión. ¡Ah!, en el post he mencionado siempre, para simplificar, malloc(), pero, como todos saben, la asignación dinámica de memoria es una familia de funciones (malloc (), calloc (), realloc () y free()) que permiten una gran flexibilidad y variedad de soluciones.
Bueno, esto es todo, y recordad: por cada malloc() hay un free(): si las cuentas no te salen empieza a preocuparte...
Hasta el próximo post.