c++ - delete - ¿Por qué std:: allocator:: deallocate requiere un tamaño?
malloc c++ (5)
std::allocator
es una abstracción sobre el modelo de memoria subyacente, que envuelve la funcionalidad de llamar new
y delete
. delete
embargo, delete
no necesita un tamaño, pero deallocate() requiere .
desasignar desasignación (T * p, std :: size_t n);
"El argumento n debe ser igual al primer argumento de la llamada a allocate () que originalmente produjo p; de lo contrario, el comportamiento no está definido".
¿Por qué?
Ahora tengo que hacer cálculos adicionales antes de desasignar o comenzar a almacenar los tamaños que pasé para asignar. Si no usara el asignador, no tendría que hacer esto.
A menudo es útil que los algoritmos de asignación de memoria minimicen la cantidad de sobrecarga que requieren. Algunos algoritmos que realizan un seguimiento de las áreas libres en lugar de las áreas asignadas pueden reducir la cantidad total de sobrecarga a un valor constante bajo con una sobrecarga de cero por bloque (la información de contabilidad se almacena completamente dentro de las áreas libres). En los sistemas que utilizan dichos algoritmos, una solicitud de asignación elimina el almacenamiento del grupo libre, y una solicitud de desasignación agrega almacenamiento al grupo libre.
Si las solicitudes de asignación para 256 y 768 bytes se satisfacen utilizando una región contigua de la agrupación, el estado del administrador de memoria sería idéntico a lo que sería si se hubieran satisfecho dos solicitudes de 512 bytes utilizando esa misma región. Si al administrador de memoria se le pasara un puntero al primer bloque y se le pidiera que lo liberara, no tendría forma de saber si la primera solicitud había sido para 256 bytes, o 512 bytes, o cualquier otro número, y por lo tanto no hay forma de saber ¿Cuánta memoria se debe agregar de nuevo a la piscina?
Implementar "malloc" y "gratis" en dicho sistema requeriría que almacene la longitud de cada bloque al principio de su región de almacenamiento y devuelva un puntero a la siguiente dirección adecuadamente alineada que estaría disponible después de esa longitud. Es ciertamente posible que una implementación haga eso, pero agregaría 4-8 bytes de sobrecarga a cada asignación. Si la persona que llama puede decirle a la rutina de desasignación cuánto almacenamiento agregará nuevamente al conjunto de memoria, se puede eliminar dicha sobrecarga.
Además, es bastante fácil acomodar este punto de diseño: simplemente asigne elementos como una struct
, y almacene el tamaño como un elemento dentro de esa struct
. Su código que llama al deallocator ahora sabe qué valor proporcionar, ya que la estructura en sí lo contiene.
Al hacer esto, esencialmente estás haciendo exactamente lo que cualquier otra implementación de lenguaje podría hacer amablemente por ti. Sólo estás haciendo lo mismo explícitamente.
Ahora, dado que estamos hablando de C ++ , que tiene montones de grandes clases de contenedores ya integradas, lo alentaría sinceramente a que evite "rodar las suyas" si es posible que pueda evitarlo. Simplemente encuentre una manera de usar una de las ingeniosas clases de contenedor que ya proporcionan el lenguaje y la biblioteca estándar.
De lo contrario, asegúrese de empaquetar lo que está construyendo aquí como una clase de contenedor de cosecha propia. Asegúrese de que la lógica que trata con el asignador y el desasignador se produce solo una vez en su programa. (A saber, dentro de esta clase). Espolvoréelo generosamente con una lógica que esté específicamente diseñada para detectar errores. (Por ejemplo, un valor de centinela que se inserta en un objeto cuando se asigna, y que se debe encontrar cuando el objeto se desasigna, y que se borra justo antes de que se haga. Verificaciones explícitas del valor de tamaño almacenado para hacer Seguro que tiene sentido. Y así sucesivamente.)
No es necesario que hagas un seguimiento del tamaño. El asignador estándar no realiza un seguimiento del tamaño porque asume un tamaño para todas las asignaciones. Por supuesto, hay diferentes tipos de asignadores para diferentes propósitos. Un asignador de tamaño de bloque, como puede haber adivinado, tiene un tamaño fijo. Algunas aplicaciones, como los videojuegos, asignan la memoria por adelantado y eliminan la sobrecarga de tener que realizar un seguimiento del tamaño de cada asignación.
La biblioteca estándar intenta ser lo más genérica posible. Algunos asignadores necesitan realizar un seguimiento del tamaño, otros no, pero todos los asignadores deben cumplir con la interfaz.
No tengo una prueba concluyente, pero tengo la sensación de que el asignador no está obligado a usar el operador de C ++ new / delete y también podría usar rutinas de administración de memoria que no tienen la capacidad de asignar arreglos y conocer sus tamaños, como malloc, por ejemplo.
El diseño de la API std::allocator
, el concepto de Allocator
, es facilitar el trabajo de posibles reemplazos.
std::allocator
es una abstracción sobre el modelo de memoria subyacente
¡No tiene que ser! En general, un asignador no necesita usar C malloc
y free
, ni delete
o el new
en el lugar. Sí, el predeterminado generalmente lo hace, pero el mecanismo de asignación no es simplemente una abstracción sobre el modelo de memoria de C. Ser diferente es a menudo todo el propósito de un asignador personalizado. Recuerde que los asignadores son reemplazables: es posible que un std::allocator
particular no necesite el tamaño para la desasignación, pero es probable que cualquier reemplazo lo haga.
Una implementación compatible de std::allocator
es libre de afirmar que efectivamente se pasa la n
correcta para deallocate
, y que de otro modo dependerá de que el tamaño sea correcto.
Sucede que malloc
y free
almacenan el tamaño de trozo en sus estructuras de datos. Pero en general, un asignador podría no hacerlo, y exigirlo es un pesimismo prematuro. Supongamos que tenía un asignador de grupo personalizado y estaba asignando trozos de int
s. En un sistema típico de 64 bits sería una sobrecarga del 200% almacenar un size_t
64 bits junto con el int
32 bits. El usuario del asignador está mucho mejor posicionado para almacenar el tamaño a lo largo de la asignación o para determinar el tamaño de una manera más económica.
Las buenas implementaciones de malloc no almacenan el tamaño de asignación para cada asignación pequeña; ellos y son capaces de derivar el tamaño del fragmento a partir del propio puntero, por ejemplo, derivando un puntero de bloque del puntero del fragmento, y luego inspeccionando el encabezado del bloque para el tamaño del fragmento. Eso es solo un detalle por supuesto. Puede obtener el límite inferior en el tamaño utilizando API específicas de la plataforma, como malloc_size
en OS X, _msize
en Windows, malloc_usable_size
en Linux.