Un argumento free(void *)
(introducido en Unix V7) tiene otra ventaja importante sobre los dos argumentos anteriores mfree(void *, size_t)
que no he visto mencionados aquí: un argumento free
simplifica drásticamente todas las demás API que funcionan con memoria dinámica. Por ejemplo, si se free
necesita el tamaño del bloque de memoria, entonces de strdup
alguna manera tendría que devolver dos valores (puntero + tamaño) en lugar de uno (puntero), y C hace que los retornos de valores múltiples sean mucho más engorrosos que los retornos de un solo valor. En lugar de char *strdup(char *)
tener que escribir char *strdup(char *, size_t *)
o de lo contrario struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *)
. (Hoy en día, esa segunda opción parece bastante tentadora, porque sabemos que las cadenas terminadas en NUL son el "error de diseño más catastrófico en la historia de la informática", pero eso es en retrospectiva. En los años 70, la capacidad de C para manejar cadenas como algo simple en char *
realidad se consideraba una ventaja definitoria sobre competidores como Pascal y Algol ). Además, no es solo strdup
que sufre este problema, afecta a todos los sistemas o definidos por el usuario. función que asigna memoria de pila.
Los primeros diseñadores de Unix eran personas muy inteligentes, y hay muchas razones por las que free
es mejor que mfree
eso, básicamente, creo que la respuesta a la pregunta es que se dieron cuenta de esto y diseñaron su sistema en consecuencia. Dudo que encuentres algún registro directo de lo que estaba pasando dentro de sus cabezas en el momento en que tomaron esa decisión. Pero podemos imaginar.
Imagina que estás escribiendo aplicaciones en C para que se ejecuten en V6 Unix, con sus dos argumentos mfree
. Lo ha hecho bien hasta ahora, pero realizar un seguimiento de estos tamaños de puntero se está volviendo cada vez más complicado a medida que sus programas se vuelven más ambiciosos y requieren cada vez más el uso de variables asignadas al montón. Pero entonces tienes una idea brillante: en lugar de copiar estos size_t
correos electrónicos todo el tiempo, puedes escribir algunas funciones de utilidad, que guardan el tamaño directamente dentro de la memoria asignada:
void *my_alloc(size_t size) {
void *block = malloc(sizeof(size) + size);
*(size_t *)block = size;
return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
block = (size_t *)block - 1;
mfree(block, *(size_t *)block);
}
Y cuanto más código escriba con estas nuevas funciones, más impresionantes parecerán. No solo hacen que su código sea más fácil de escribir, sino que también hacen que su código sea más rápido , ¡dos cosas que no suelen ir juntas! Antes, pasaba estos size_t
mensajes por todas partes, lo que agregaba sobrecarga de CPU para la copia, y significaba que tenía que derramar registros con más frecuencia (especialmente para los argumentos de función adicionales) y memoria desperdiciada (ya que las llamadas de funciones anidadas a menudo resultarán en múltiples copias del size_t
almacenado en diferentes marcos de pila). En su nuevo sistema, todavía tiene que gastar la memoria para almacenar elsize_t
, pero solo una vez, y nunca se copia en ningún lado. Estas pueden parecer pequeñas eficiencias, pero tenga en cuenta que estamos hablando de máquinas de gama alta con 256 KiB de RAM.
¡Esto te hace feliz! Así que comparte su genial truco con los hombres barbudos que están trabajando en el próximo lanzamiento de Unix, pero no los hace felices, los entristece. Verá, estaban en el proceso de agregar un montón de nuevas funciones de utilidad como strdup
, y se dan cuenta de que las personas que usan su truco genial no podrán usar sus nuevas funciones, porque todas sus nuevas funciones usan el engorroso puntero + tamaño API. Y eso también te entristece, porque te das cuenta de que tendrás que reescribir strdup(char *)
tú mismo la buena función en cada programa que escribas, en lugar de poder usar la versión del sistema.
¡Pero espera! ¡Estamos en 1977 y la compatibilidad con versiones anteriores no se inventará hasta dentro de 5 años! Y además, nadie en serio usa esta cosa oscura de "Unix" con su nombre subido de tono. La primera edición de K&R está en camino al editor ahora, pero eso no es un problema - dice en la primera página que "C no proporciona operaciones para tratar directamente con objetos compuestos como cadenas de caracteres ... no hay montón ... ". En este punto de la historia, string.h
ya malloc
están las extensiones de proveedor (!). Entonces, sugiere Bearded Man # 1, podemos cambiarlos como queramos; ¿Por qué no simplemente declaramos que su difícil asignador es el asignador oficial ?
Unos días más tarde, Bearded Man # 2 ve la nueva API y dice oye, espera, esto es mejor que antes, pero todavía está gastando una palabra entera por asignación almacenando el tamaño. Él ve esto como lo próximo a la blasfemia. Todos los demás lo miran como si estuviera loco, porque ¿qué más puedes hacer? Esa noche se queda hasta tarde e inventa un nuevo asignador que no almacena el tamaño en absoluto, sino que lo infiere sobre la marcha al realizar cambios de bits de magia negra en el valor del puntero, y lo intercambia mientras mantiene la nueva API en su lugar. La nueva API significa que nadie se da cuenta del cambio, pero sí notan que a la mañana siguiente el compilador usa un 10% menos de RAM.
Y ahora todos están felices: obtienes tu código más fácil de escribir y más rápido, Bearded Man # 1 puede escribir un simple y agradable strdup
que la gente realmente usará, y Bearded Man # 2, confiado en que se ha ganado su sustento por un tiempo. - vuelve a jugar con quines . ¡Envíalo!
O al menos, así pudo haber sucedido.