¿Por qué "free" en C no toma el número de bytes que se van a liberar?

84

Para que quede claro: lo sé mallocy freese implementan en la biblioteca C, que generalmente asigna fragmentos de memoria del sistema operativo y hace su propia administración para distribuir lotes más pequeños de memoria en la aplicación y realiza un seguimiento del número de bytes asignados . Esta pregunta no es ¿Cómo saber gratis cuánto liberar ?

Más bien, quiero saber por qué freese hizo de esta manera en primer lugar. Al ser un lenguaje de bajo nivel, creo que sería perfectamente razonable pedirle a un programador de C que realice un seguimiento no solo de la memoria asignada, sino de la cantidad (de hecho, normalmente encuentro que termino realizando un seguimiento de la cantidad de bytes malloced de todos modos). También se me ocurre que dar explícitamente la cantidad de bytes freepodría permitir algunas optimizaciones de rendimiento, por ejemplo, un asignador que tiene grupos separados para diferentes tamaños de asignación podría determinar de qué grupo liberarse con solo mirar los argumentos de entrada, y en general, habría menos espacio sobre la cabeza.

Entonces, en resumen, ¿por qué se crearon mallocy se freecrearon de tal manera que se requiere que realicen un seguimiento interno de la cantidad de bytes asignados? ¿Es solo un accidente histórico?

Una pequeña edición: algunas personas han proporcionado puntos como "¿qué pasa si liberas una cantidad diferente a la que asignaste?". Mi API imaginada podría simplemente requerir una para liberar exactamente la cantidad de bytes asignados; liberar más o menos podría ser simplemente UB o una implementación definida. Sin embargo, no quiero desalentar la discusión sobre otras posibilidades.

jaymmer - Restablecer a Monica
fuente
18
Porque ya es una molestia realizar un seguimiento de las asignaciones en sí mismas, e incluso complicaría más el código si además tuviera que realizar un seguimiento del tamaño.
Jens Gustedt
11
Puedo pensar en varias razones: ¿Por qué hacer que el usuario lo haga si no es necesario? ¿Qué pasa si el usuario lo estropea? De todos modos, es una pregunta redundante. Si hubieran tomado la otra opción, todavía estaría preguntando por qué.
BoBTFish
15
@BoBTFish: Estamos hablando de C , no de Python o incluso de C ++. El usuario ya tiene que hacer $ h! 1 tonelada que no tiene que hacerlo. Ésa no es una razón.
user541686
4
K&R tampoco tiene nada que decir al respecto. Podemos especular todo lo que queramos, pero creo que la razón original se puede perder en la historia .
BoBTFish
35
No puede exigir que el programador pase correctamente el tamaño del bloque, porque la persona que llama mallocno conoce el tamaño del bloque devuelto . malloca menudo devuelve un bloque que es más grande de lo solicitado. En el mejor de los casos, el programador podría pasar el tamaño solicitado en la malloc()llamada, lo que no ayudaría free()en absoluto al implementador de .
Ben Voigt

Respuestas:

97

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 freesimplifica drásticamente todas las demás API que funcionan con memoria dinámica. Por ejemplo, si se freenecesita el tamaño del bloque de memoria, entonces de strdupalguna 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 strdupque 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 freees mejor que mfreeeso, 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_tcorreos 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_tmensajes 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_talmacenado 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.hya mallocestá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 strdupque 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.

Nathaniel J. Smith
fuente
14
Err, en caso de que no esté claro, esto es un vuelo de fantasía, con detalles que corroboran para proporcionar versatilidad artística. Cualquier parecido con personas vivas o muertas se debe simplemente a que todos los involucrados parecían iguales . No lo confunda con la historia real.
Nathaniel J. Smith
5
Tú ganas. Esta me parece la explicación más plausible (y el mejor escrito). Incluso si todo aquí se demuestra incorrecto o inválido, esta es la mejor respuesta para las excelentes representaciones de hombres barbudos.
jaymmer - Reincorpora a Monica
Vaya, una respuesta en esta página que realmente suena plausible. +1 de mi parte.
user541686
Aún mejor: ¡una respuesta en esta página que es realmente agradable! +1 también.
David C. Rankin
Me pregunto si algún sistema Pascal notable utilizó un conjunto de cadenas de recolección de basura de una manera similar a los intérpretes BASIC de microcomputadoras. La semántica de C no funcionaría con tal cosa, pero en Pascal tal cosa podría manejarse bastante bien si el código mantuviera marcos de pila rastreables (lo que muchos compiladores hicieron de todos modos).
supercat
31

"¿Por qué freeen C no se necesita el número de bytes a liberar?"

Porque no es necesario, y de todos modos no tendría mucho sentido .

Cuando asigna algo, quiere decirle al sistema cuántos bytes asignar (por razones obvias).

Sin embargo, cuando ya haya asignado su objeto, ahora se determina el tamaño de la región de memoria que recupera. Está implícito. Es un bloque de memoria contiguo. No puede desasignar parte de él (olvidemos realloc(), eso no es lo que está haciendo de todos modos), solo puede desasignar todo. Tampoco puede "desasignar X bytes": o libera el bloque de memoria del que obtuvo malloc()o no lo hace.

Y ahora, si desea liberarlo, puede decirle al sistema de administración de memoria: "Aquí está este puntero, free()el bloque al que apunta". - y el administrador de memoria sabrá cómo hacerlo, ya sea porque conoce implícitamente el tamaño o porque es posible que ni siquiera lo necesite.

Por ejemplo, las implementaciones más típicas de malloc()mantener una lista vinculada de punteros a bloques de memoria libres y asignados. Si pasa un puntero a free(), simplemente buscará ese puntero en la lista "asignada", desvinculará el nodo correspondiente y lo adjuntará a la lista "libre". Ni siquiera necesitaba el tamaño de la región. Solo necesitará esa información cuando potencialmente intente reutilizar el bloque en cuestión.

El croissant paramagnético
fuente
Si te pido prestados $ 100 y luego te presto $ 100 nuevamente y luego lo hago otras cinco veces, ¿realmente te importa que te haya pedido prestado dinero siete veces (a menos que realmente estés cobrando intereses!)? ¿O simplemente te importa que te haya pedido prestados $ 700? Lo mismo aquí: el sistema solo se preocupa por la memoria que no está asignada, no le importa (y no necesita ni debería) cómo se divide la memoria asignada.
user541686
1
@Mehrdad: No, y no es así. C, sin embargo, sí. Todo su propósito es hacer las cosas (un poco) más seguras. Realmente no sé lo que estás buscando aquí.
Lightness Races in Orbit
12
@ user3477950: No es necesario pasar el número de bytes : Sí, porque fue diseñado de esta manera. El OP preguntó ¿Por qué fue diseñado de esta manera?
Desduplicador
5
"Porque no es necesario": también podría haber sido diseñado para que lo necesite.
user253751
5
@Mehrdad que una analogía completamente confusa y defectuosa. Si asigna 4 bytes cien veces, definitivamente importa exactamente cuál libera. liberar el primero no es lo mismo que liberar el segundo. con el dinero, por otro lado, no importa si paga el primero o el segundo préstamo primero, es solo una gran pila
user2520938
14

Es posible que C no sea tan "abstracto" como C ++, pero aún así se pretende que sea una abstracción sobre el ensamblaje. Con ese fin, los detalles del nivel más bajo se eliminan de la ecuación. Esto evita que tenga que perder el tiempo con la alineación y el relleno, en su mayor parte, lo que haría que todos sus programas en C no fueran portátiles.

En resumen, este es el objetivo de escribir una abstracción .

Carreras de ligereza en órbita
fuente
9
No estoy seguro de qué alineación o acolchado tienen que ver con esto. La respuesta realmente no responde a nada.
user541686
1
@Mehrdad C no es un lenguaje x86, intenta (más o menos) ser portable y así libera al programador de esa importante carga. Puede alcanzar ese nivel de todos modos de varias otras formas (por ejemplo, ensamblaje en línea) pero la abstracción es la clave. Estoy de acuerdo con esta respuesta.
Marco A.
4
@Mehrdad: si solicita mallocN bytes y, en cambio, devuelve un puntero al principio de una página completa (debido a la alineación, el relleno u otras restricciones , no habría forma de que el usuario lo rastreara, obligándolos a hacerlo sería contraproducente.
Michael Foukarakis
3
@MichaelFoukarakis: mallocsimplemente siempre puede devolver un puntero alineado sin almacenar el tamaño de la asignación. freeluego podría redondearse a la alineación adecuada para garantizar que todo se libere correctamente. No veo dónde está el problema.
user541686
6
@Mehrdad: No hay ningún beneficio por todo ese trabajo extra que acabas de mencionar. Además, pasar un sizeparámetro a freeabre otra fuente de errores.
Michael Foukarakis
14

En realidad, en el antiguo asignador de memoria del núcleo de Unix, mfree()tomó un sizeargumento. malloc()y mfree()mantuvo dos matrices (una para la memoria central y otra para el intercambio) que contenían información sobre direcciones y tamaños de bloques libres.

No había un asignador de espacio de usuario hasta Unix V6 (los programas solo lo usarían sbrk()). En Unix V6, iolib incluía un asignador con alloc(size)y una free()llamada que no tomaba un argumento de tamaño. Cada bloque de memoria estaba precedido por su tamaño y un puntero al siguiente bloque. El puntero solo se usó en bloques libres, al recorrer la lista libre, y se reutilizó como memoria de bloque en bloques en uso.

En Unix 32V y en Unix V7, este fue sustituido por un nuevo malloc()e free()implementación, donde free()no hacía falta ser un sizeargumento. La implementación era una lista circular, cada fragmento estaba precedido por una palabra que contenía un puntero al siguiente fragmento y un bit "ocupado" (asignado). Por lo tanto, malloc()/free()ni siquiera realizó un seguimiento de un tamaño explícito.

ninjalj
fuente
10

¿Por qué freeen C no se necesita el número de bytes a liberar?

Porque no es necesario. La información ya está disponible en la gestión interna realizada por malloc / free.

Aquí hay dos consideraciones (que pueden haber contribuido o no a esta decisión):

  • ¿Por qué esperaría que una función reciba un parámetro que no necesita?

    (esto complicaría prácticamente todo el código de cliente que depende de la memoria dinámica y agregaría una redundancia completamente innecesaria a su aplicación). Hacer un seguimiento de la asignación de punteros ya es un problema difícil. Hacer un seguimiento de las asignaciones de memoria junto con los tamaños asociados aumentaría innecesariamente la complejidad del código del cliente.

  • ¿Qué haría la freefunción alterada en estos casos?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    ¿ No sería gratis (causaría una pérdida de memoria?)? ¿Ignora el segundo parámetro? ¿Detener la aplicación llamando a exit? Implementar esto agregaría puntos de falla adicionales en su aplicación, para una característica que probablemente no necesite (y si la necesita, vea mi último punto, a continuación: "implementación de la solución a nivel de aplicación").

Más bien, quiero saber por qué se hizo gratis de esta manera en primer lugar.

Porque esta es la forma "correcta" de hacerlo. Una API debe requerir los argumentos que necesita para realizar su operación, y nada más .

También se me ocurre que dar explícitamente la cantidad de bytes libres podría permitir algunas optimizaciones de rendimiento, por ejemplo, un asignador que tenga grupos separados para diferentes tamaños de asignación podría determinar de qué grupo liberarse con solo mirar los argumentos de entrada, y habría menos espacio por encima de la cabeza en general.

Las formas adecuadas de implementar eso son:

  • (a nivel del sistema) dentro de la implementación de malloc: no hay nada que impida que el implementador de la biblioteca escriba malloc para usar varias estrategias internamente, según el tamaño recibido.

  • (a nivel de aplicación) envolviendo malloc y free dentro de sus propias API, y usándolas en su lugar (en todas partes de su aplicación que pueda necesitar).

utnapistim
fuente
6
@ user3477950: No es necesario pasar el número de bytes : Sí, porque fue diseñado de esta manera. El OP preguntó ¿Por qué fue diseñado de esta manera?
Desduplicador
4
"Porque no es necesario": también podría haber sido diseñado para que lo necesite, al no guardar esa información.
user253751
1
En cuanto a su punto 2, me pregunto si libre (NULO) es un comportamiento definido. Aha, "Todas las normas versiones compatibles de la biblioteca C convite libre (NULL) como un no-op" - fuente stackoverflow.com/questions/1938735/...
MAWG dice restablecer Mónica
10

Me vienen a la mente cinco razones:

  1. Es conveniente. Elimina una carga completa de sobrecarga del programador y evita una clase de errores extremadamente difíciles de rastrear.

  2. Abre la posibilidad de soltar parte de un bloque. Pero dado que los administradores de memoria generalmente quieren tener información de seguimiento, no está claro qué significaría esto.

  3. Lightness Races In Orbit es acertado sobre el relleno y la alineación. La naturaleza de la gestión de la memoria significa que el tamaño real asignado es muy posiblemente diferente del tamaño que solicitó. Esto significa que si se freerequiere un tamaño y una ubicación, mallocse tendría que cambiar para devolver también el tamaño real asignado.

  4. De todos modos, no está claro que haya algún beneficio real de pasar el tamaño. Un administrador de memoria típico tiene de 4 a 16 bytes de encabezado para cada porción de memoria, que incluye el tamaño. Este encabezado de fragmento puede ser común para la memoria asignada y no asignada y, cuando los fragmentos adyacentes se liberan, se pueden contraer juntos. Si está haciendo que la persona que llama almacene la memoria libre, puede liberar probablemente 4 bytes por fragmento al no tener un campo de tamaño separado en la memoria asignada, pero ese campo de tamaño probablemente no se gane de todos modos, ya que la persona que llama necesita almacenarlo en algún lugar. Pero ahora esa información está dispersa en la memoria en lugar de estar ubicada de manera predecible en el fragmento de encabezado, que probablemente sea menos eficiente desde el punto de vista operativo de todos modos.

  5. Incluso si fuera más eficiente, es radicalmente poco probable que su programa esté gastando una gran cantidad de tiempo liberando memoria de todos modos, por lo que el beneficio sería mínimo.

Por cierto, su idea sobre asignadores separados para artículos de diferentes tamaños se implementa fácilmente sin esta información (puede usar la dirección para determinar dónde ocurrió la asignación). Esto se hace habitualmente en C ++.

Agregado más tarde

Otra respuesta, bastante ridícula, ha presentado std :: allocator como prueba de que freepodría funcionar de esta manera pero, de hecho, sirve como un buen ejemplo de por qué freeno funciona de esta manera. Hay dos diferencias clave entre qué malloc/ freehacer y qué hace std :: allocator. En primer lugar, mallocy freese enfrenta el usuario - que están diseñados para los programadores generales para trabajar con - mientras que std::allocatorestá diseñado para proporcionar la asignación de memoria especialista para la biblioteca estándar. Esto proporciona un buen ejemplo de cuándo el primero de mis puntos no importa o no importa. Dado que es una biblioteca, las dificultades de manejar las complejidades del tamaño de seguimiento están ocultas al usuario de todos modos.

En segundo lugar, std :: allocator siempre funciona con el mismo tamaño de elemento, lo que significa que es posible utilizar el número de elementos pasados ​​originalmente para determinar cuánto de libre. Por qué esto difiere de freesí mismo es ilustrativo. En std::allocatorlos artículos a asignar son siempre del mismo, conocido, tamaño y siempre del mismo tipo de artículo por lo que siempre tienen el mismo tipo de requisitos de alineación. Esto significa que el asignador podría estar especializado para simplemente asignar una matriz de estos elementos al principio y distribuirlos según sea necesario. No podría hacer esto con freeporque no hay forma de garantizar que el mejor tamaño para devolver sea el tamaño solicitado, en cambio, es mucho más eficiente devolver bloques más grandes de lo que pide la persona que llama * y, por lo tanto , tampocoel usuario o el administrador debe realizar un seguimiento del tamaño exacto realmente otorgado. Pasar este tipo de detalles de implementación al usuario es un dolor de cabeza innecesario que no beneficia a la persona que llama.

- * Si alguien todavía tiene dificultades para comprender este punto, considere lo siguiente: un asignador de memoria típico agrega una pequeña cantidad de información de seguimiento al inicio de un bloque de memoria y luego devuelve un desplazamiento de puntero a partir de esto. La información almacenada aquí generalmente incluye punteros al siguiente bloque libre, por ejemplo. Supongamos que el encabezado tiene solo 4 bytes de longitud (que en realidad es más pequeño que la mayoría de las bibliotecas reales) y no incluye el tamaño, luego imaginemos que tenemos un bloque libre de 20 bytes cuando el usuario solicita un bloque de 16 bytes, un ingenuo El sistema devolvería el bloque de 16 bytes, pero luego dejaría un fragmento de 4 bytes que nunca podría usarse perdiendo el tiempo cada vez.mallocse llama. Si, en cambio, el administrador simplemente devuelve el bloque de 20 bytes, entonces evita que estos fragmentos desordenados se acumulen y puede asignar de manera más limpia la memoria disponible. Pero si el sistema debe hacer esto correctamente sin rastrear el tamaño en sí, entonces requerimos que el usuario rastree, para cada asignación única, la cantidad de memoria realmente asignada para devolverla gratis. El mismo argumento se aplica al relleno para tipos / asignaciones que no coinciden con los límites deseados. De este modo, a lo sumo, lo que requiere freetomar un tamaño puede ser (a) poco útil ya que el asignador de memoria no puede basarse en el tamaño pasado para que coincida con el tamaño realmente asignado o (b) requiere que el usuario sin sentido para hacer el trabajo de seguimiento de la verdadera tamaño que sería fácilmente manejado por cualquier administrador de memoria sensato.

Jack Aidley
fuente
# 1 es cierto. # 2: no estoy seguro de lo que quieres decir. No estoy tan seguro del número 3, la alineación no requiere almacenar información adicional. # 4 es un razonamiento circular; los administradores de memoria solo requieren esa sobrecarga por fragmento porque almacenan el tamaño, por lo que no puede usar eso como un argumento de por qué almacenan el tamaño. Y el # 5 es muy discutible.
user541686
Acepté y luego no acepté; esta parece una buena respuesta, pero la pregunta parece estar recibiendo mucha actividad, creo que podría haber sido un poco prematuro. Sin embargo, esto definitivamente me da algo en qué pensar.
jaymmer - Reincorpora a Monica
2
@jaymmer: Sí, es prematuro. Sugiero esperar más de uno o dos días antes de aceptar, y sugiero que lo piense por su cuenta. Es una pregunta realmente interesante y la mayoría / todas las respuestas que obtendrá al principio para cualquier pregunta de "por qué" en StackOverflow serán solo intentos semiautomáticos de justificar el sistema actual en lugar de abordar realmente la pregunta subyacente.
user541686
@Mehrdad: Has entendido mal lo que digo en el n. ° 4. No estoy diciendo que tomará un tamaño adicional, estoy diciendo que (a) moverá quién tiene que almacenar el tamaño, por lo que en realidad no ahorrará espacio y (b) es probable que el cambio resultante lo haga menos eficiente no más. En cuanto al número 5, no estoy convencido de que sea discutible en absoluto: estamos, como mucho, hablando de guardar un par de instrucciones de la llamada gratuita. En comparación con los costos de la llamada gratuita, será minúsculo.
Jack Aidley
1
@Mehrdad: Ah, y en el # 3, no, no requiere información adicional, requiere memoria adicional. Un administrador de memoria típico que está diseñado para trabajar con alineación de 16 bytes devolverá un puntero a un bloque de 128 bytes cuando se le solicite un bloque de 115 bytes. Si la freellamada va a pasar correctamente el tamaño para ser liberada, debe saber esto.
Jack Aidley
5

Solo publico esto como una respuesta, no porque sea la que estás esperando, sino porque creo que es la única plausiblemente correcta:

Probablemente se consideró conveniente originalmente y no se pudo mejorar a partir de entonces.
Probablemente no haya una razón convincente para ello. (Pero felizmente eliminaré esto si se muestra que es incorrecto).

No sería haber beneficios si era posible: se podría asignar un solo pedazo grande de memoria cuyo tamaño se sabía de antemano, a continuación, liberar un poco a la vez - en lugar de asignar y liberar pequeños trozos de la memoria en varias ocasiones. Actualmente, tareas como esta no son posibles.


Para los muchos (¡muchos 1 !) De ustedes que piensan que pasar el tamaño es tan ridículo:

¿Puedo remitirlo a la decisión de diseño de C ++ para el std::allocator<T>::deallocatemétodo?

void deallocate(pointer p, size_type n);

Todos los objetos en el área señalada por serán destruidos antes de esta llamada. coincidirá con el valor pasado para obtener esta memoria.n Tp
nallocate

Creo que pasará un tiempo bastante "interesante" analizando esta decisión de diseño.


En cuanto a operator delete, resulta que la propuesta N3778 de 2013 (" Desasignación de tamaño de C ++") también tiene la intención de arreglar eso.


1 Solo mire los comentarios debajo de la pregunta original para ver cuántas personas hicieron afirmaciones apresuradas como "el tamaño solicitado es completamente inútil para la freellamada" para justificar la falta del sizeparámetro.

usuario541686
fuente
5
Para la malloc()implementación, también eliminaría la necesidad de recordar cualquier cosa sobre una región asignada, reduciendo la sobrecarga de asignación a la sobrecarga de alineación. malloc()podría hacer toda la contabilidad dentro de los propios trozos liberados. Hay casos de uso en los que esto sería una gran mejora. Sin embargo, debería desalentarse la desasignación de una gran parte de la memoria en varios fragmentos pequeños, lo que aumentaría drásticamente la fragmentación.
cmaster - reinstalar a monica
1
@cmaster: Esos casos de uso eran exactamente del tipo al que me refería, has dado en el clavo, gracias. Con respecto a la fragmentación: no estoy seguro de cómo eso aumenta la fragmentación en relación con la alternativa, que es asignar y liberar memoria en pequeños fragmentos.
user541686
std::allocatorasigna solo elementos de un tamaño específico y conocido. No es un asignador de propósito general, la comparación es manzanas con naranjas.
Jack Aidley
Me parece que se tomó una decisión en parte filosófica y en parte de diseño para hacer de la biblioteca estándar en C un conjunto mínimo de primitivas a partir del cual se puede construir prácticamente cualquier cosa, originalmente pensada como un lenguaje de nivel de sistemas y portátil a muchos sistemas que este enfoque primitivo hace sentido. Con C ++, se tomó una decisión diferente para hacer que la biblioteca estándar sea muy extensa (y más grande con C ++ 11). Un hardware de desarrollo más rápido, memorias más grandes, arquitecturas más complejas y la necesidad de abordar el desarrollo de aplicaciones de nivel superior quizás contribuyan a este cambio de énfasis.
Clifford
1
@Clifford: Exactamente, por eso dije que no hay una razón convincente para ello. Es solo una decisión que se tomó y no hay razón para creer que sea estrictamente mejor que las alternativas.
user541686
2

malloc y free van de la mano, con cada "malloc" emparejado con uno "free". Por lo tanto, tiene mucho sentido que la coincidencia "gratuita" con un "malloc" anterior debería simplemente liberar la cantidad de memoria asignada por ese malloc; este es el caso de uso mayoritario que tendría sentido en el 99% de los casos. Imagine todos los errores de memoria si todos los usos de malloc / free por parte de todos los programadores del mundo alguna vez, necesitaran que el programador hiciera un seguimiento de la cantidad asignada en malloc y luego recuerde liberar el mismo. El escenario del que habla realmente debería usar múltiples mallocs / free en algún tipo de implementación de administración de memoria.

Marius George
fuente
5
Creo que "Imagine todos los [...] errores" es discutible cuando piensa en otras fábricas de errores como gets, printfbucles manuales (uno por uno), comportamientos indefinidos, cadenas de formato, conversiones implícitas, trucos de bits, etc. cetera.
Sebastian Mach
1

Sugeriría que se debe a que es muy conveniente no tener que rastrear manualmente la información de tamaño de esta manera (en algunos casos) y también es menos propenso a errores del programador.

Además, realloc necesitaría esta información contable, que espero que contenga más que solo el tamaño de la asignación. es decir, permite definir la implementación del mecanismo por el que trabaja.

Sin embargo, podría escribir su propio asignador que funcionó de alguna manera de la manera que sugiere y, a menudo, se hace en c ++ para asignadores de grupo de una manera similar para casos específicos (con ganancias de rendimiento potencialmente masivas), aunque esto generalmente se implementa en términos de operador nuevo para la asignación de bloques de piscina.

Pete
fuente
1

No veo cómo funcionaría un asignador que no rastrea el tamaño de sus asignaciones. Si no hiciera esto, ¿cómo sabría qué memoria está disponible para satisfacer una mallocsolicitud futura ? Tiene que almacenar al menos algún tipo de estructura de datos que contenga direcciones y longitudes, para indicar dónde están los bloques de memoria disponibles. (Y, por supuesto, almacenar una lista de espacios libres equivale a almacenar una lista de espacios asignados).

MM
fuente
Ni siquiera necesita un campo de tamaño explícito. Solo puede tener un puntero al siguiente bloque y un bit asignado.
ninjalj
0

Bueno, lo único que necesita es un puntero que usará para liberar la memoria que asignó anteriormente. La cantidad de bytes es algo que administra el sistema operativo, por lo que no tiene que preocuparse por ello. No sería necesario obtener la cantidad de bytes asignados devueltos por free (). Le sugiero una forma manual de contar el número de bytes / posiciones asignados por un programa en ejecución:

Si trabaja en Linux y desea saber la cantidad de bytes / posiciones que malloc ha asignado, puede crear un programa simple que use malloc una o n veces e imprima los punteros que obtiene. Además, debes hacer que el programa duerma durante unos segundos (lo suficiente para que puedas hacer lo siguiente). Después de eso, ejecute ese programa, busque su PID, escriba cd / proc / process_PID y simplemente escriba "cat maps". La salida le mostrará, en una línea específica, las direcciones de memoria inicial y final de la región de memoria del montón (aquella en la que está asignando memoria dinámicamente) .Si imprime los punteros a estas regiones de memoria que se están asignando, puede adivinar cuánta memoria ha asignado.

¡Espero eso ayude!


fuente
0

¿Por qué debería hacerlo? malloc () y free () son primitivas de administración de memoria intencionalmente muy simples , y la administración de memoria de alto nivel en C depende en gran medida del desarrollador. T

Además, realloc () ya lo hace, si reduce la asignación en realloc () no moverá los datos y el puntero devuelto será el mismo que el original.

En general, es cierto que toda la biblioteca estándar está compuesta de primitivas simples a partir de las cuales puede construir funciones más complejas para satisfacer las necesidades de su aplicación. Entonces, la respuesta a cualquier pregunta del tipo "por qué la biblioteca estándar no hace X" es porque no puede hacer todo lo que un programador podría pensar (para eso están los programadores), por lo que elige hacer muy poco: construir la suya propia o utilizar bibliotecas de terceros. Si desea una biblioteca estándar más extensa, incluida una administración de memoria más flexible, C ++ puede ser la respuesta.

Etiquetó la pregunta C ++ y C, y si C ++ es lo que está usando, entonces difícilmente debería usar malloc / free en cualquier caso; aparte de new / delete, las clases de contenedor STL administran la memoria automáticamente y de una manera probable para ser específicamente apropiado a la naturaleza de los diversos contenedores.

Clifford
fuente
Además, realloc () ya lo hace, si reduce la asignación en realloc () no moverá los datos y el puntero devuelto será el mismo que el original. ¿Es este comportamiento garantizado? Parece ser común en varios asignadores integrados, pero no estaba seguro de si este comportamiento se especificaba como parte de standard-c.
rsaxvc
@rsaxvc: Buena pregunta: cplusplus.com documenta "Si el nuevo tamaño es mayor, el valor de la porción recién asignada es indeterminado". , lo que implica que si es más pequeño está determinado . [opengroup.org () dice "Si el nuevo tamaño del objeto de memoria requiere el movimiento del objeto, se libera el espacio para la instanciación previa del objeto". - si fuera más pequeño, no sería necesario mover los datos. Una vez más, la implicación es que los más pequeños no se reasignarán. No estoy seguro de lo que dice la norma ISO.
Clifford
1
reallocestá absolutamente permitido mover los datos. Según el estándar C, es totalmente legal implementarlo realloccomo malloc+ memcpy+ free. Y hay buenas razones por las que una implementación podría querer mover una asignación que se ha reducido en tamaño, por ejemplo, para evitar fragmentar la memoria.
Nathaniel J. Smith