¿Por qué uno reemplazaría los operadores predeterminados new y delete?


¿Por qué debería reemplazar el operador predeterminado new y delete con un operador personalizado new y delete?

Esto es una continuación de Sobrecargar nuevo y eliminar en la inmensamente iluminadora C++ FAQ:
Operador sobrecargando.

Una entrada de seguimiento a esta FAQ es:
¿Cómo debo escribir operadores personalizados new y delete conformes al estándar ISO C++?

Nota: La respuesta está basada sobre las lecciones del C++más efectivo de Scott Meyers.
(Nota: Esta es una entrada para Stack Overflow's C++ FAQ. Si quieres criticar la idea de proporcionar un FAQ en esta forma, entonces la publicación en meta que comenzó todo esto sería el lugar para hacerlo. Las respuestas a esa pregunta se monitorean en la sala de chat de C++ , donde la idea de las preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos que se les ocurrió idea.)

Author: Community, 2011-08-22

7 answers

Uno puede intentar reemplazar los operadores new y delete por una serie de razones, a saber:

Para Detectar Errores de Uso:

Hay un número de maneras en que el uso incorrecto de new y delete puede llevar a las bestias temidas de Comportamiento Indefinido & Fugas de memoria . Los ejemplos respectivos de cada uno son:
Usar más de una deleteen new memoria ed y no llamar a delete en memoria asignada usando new.
Un operador sobrecargado new puede mantener una lista de las direcciones asignadas y el operador sobrecargado delete pueden eliminar direcciones de la lista, entonces es fácil detectar dichos errores de uso.

De manera similar, una variedad de errores de programación pueden llevar a sobrecostos de datos (escribir más allá del final de un bloque asignado) y infravaloramientos (escribir antes del comienzo de un bloque asignado).
Un operador sobrecargado new puede sobreasignar bloques y poner patrones de bytes conocidos ("firmas") antes y después de la memoria realizada disponible para los clientes. Las eliminaciones del operador sobrecargado pueden verificar si las firmas siguen intactas. Por lo tanto, al verificar si estas firmas no están intactas, es posible determinar que se produjo un rebasamiento o una falta de ejecución en algún momento durante la vida útil del bloque asignado, y operator delete puede registrar ese hecho, junto con el valor del puntero ofensivo, lo que ayuda a proporcionar una buena información de diagnóstico.


Para Mejorar la Eficiencia (velocidad y memoria):

El los operadores new y delete funcionan razonablemente bien para todos, pero de manera óptima para nadie. Este comportamiento surge del hecho de que están diseñados solo para uso general. Tienen que acomodar patrones de asignación que van desde la asignación dinámica de unos pocos bloques que existen durante la duración del programa hasta la asignación constante y la desasignación de un gran número de objetos de corta duración. Eventualmente, el operador new y el operador delete que envían con los compiladores toman un camino intermedio estrategia.

Si tiene una buena comprensión de los patrones de uso de memoria dinámica de su programa, a menudo puede encontrar que las versiones personalizadas de operator new y operator delete superan (más rápido en rendimiento, o requieren menos memoria hasta un 50%)a las predeterminadas. Por supuesto, a menos que esté seguro de lo que está haciendo, no es una buena idea hacer esto(ni siquiera intente esto si no entiende las complejidades involucradas).


Para Recopilar Estadísticas de Uso:

Antes pensando en reemplazar new y delete para mejorar la eficiencia como se menciona en #2, debe recopilar información sobre cómo su aplicación/programa utiliza la asignación dinámica. Es posible que desee recopilar información sobre:
Distribución de los bloques de asignación
Distribución de las vidas,
Orden de las asignaciones (FIFO o LIFO o aleatorio),
La comprensión de los patrones de uso cambia durante un período de tiempo, la cantidad máxima de memoria dinámica utilizada, etc.

También, a veces es posible que necesite recopilar información de uso como:
Contar el número de objetos dinámicamente de una clase,
Restringir el número de objetos que se crean usando asignación dinámica, etc.

Todos, esta información se puede recopilar reemplazando los new y delete personalizados y agregando el mecanismo de recolección de diagnóstico en los new y delete sobrecargados.


Para compensar la alineación de memoria subóptima en new:

Muchas arquitecturas de computadoras requieren que los datos de los tipos se colocan en la memoria en tipos particulares de direcciones. Por ejemplo, una arquitectura puede requerir que los punteros se produzcan en direcciones que sean múltiplos de cuatro (es decir, estar alineados con cuatro bytes) o que los dobles se produzcan en direcciones que sean múltiplos de ocho (es decir, estar alineados con ocho bytes). El incumplimiento de estas restricciones puede dar lugar a excepciones de hardware en tiempo de ejecución. Otras arquitecturas son más indulgentes, y pueden permitir que funcione aunque reduciendo el rendimiento.El operador new que el envío con algunos compiladores no garantiza la alineación de ocho bytes para dynamic asignaciones de dobles. En tales casos, reemplazar el operador predeterminado new por uno que garantice la alineación de ocho bytes podría producir grandes aumentos en el rendimiento del programa y puede ser una buena razón para reemplazar los operadores new y delete.


Para agrupar objetos relacionados cerca unos de otros:

Si sabe que las estructuras de datos particulares generalmente se usan juntas y desea minimizar la frecuencia de errores de página cuando se trabaja en los datos, puede tener sentido crear un montón separado para las estructuras de datos para que se agrupen en el menor número posible de páginas. las versiones de colocación personalizada de new y delete pueden hacer posible lograr dicha agrupación en clústeres.


Para obtener un comportamiento no convencional:

A veces desea que los operadores new y delete hagan algo que las versiones provistas por el compilador no ofrecen.
Por ejemplo: Puede escribir un operador personalizado delete que sobrescribe la memoria desasignada con ceros para aumentar la seguridad de los datos de la aplicación.

 64
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-21 19:34:28

En primer lugar, hay realmente un número de diferentes operadores new y delete (un número arbitrario, en realidad).

Primero, hay ::operator new, ::operator new[], ::operator delete y ::operator delete[]. En segundo lugar, para cualquier clase X, hay X::operator new, X::operator new[], X::operator delete y X::operator delete[].

Entre estos, es mucho más común sobrecargar los operadores específicos de clase que los operadores globales it es bastante común que el uso de memoria de una clase en particular siga un patrón lo suficientemente específico como para que pueda escribir operadores que proporcionan mejoras sustanciales sobre los impagos. Por lo general, es mucho más difícil predecir el uso de la memoria con casi esa precisión o específicamente a nivel mundial.

Probablemente también vale la pena mencionar que aunque operator new y operator new[] están separados entre sí (del mismo modo para cualquier X::operator new y X::operator new[]), no hay diferencia entre los requisitos para los dos. Uno será invocado para asignar un solo objeto, y el otro para asignar una matriz de objetos, pero cada uno todavía solo recibe una cantidad de memoria que se necesita, y necesita devolver la dirección de un bloque de memoria (al menos) que grande.

Hablando de requisitos, probablemente valga la pena revisar los otros requisitos1: los operadores globales deben ser verdaderamente globales not no se puede poner uno dentro de un espacio de nombres o hacer uno estático en una unidad de traducción en particular. En otras palabras, solo hay dos niveles en los que se pueden producir sobrecargas: una sobrecarga específica de clase o una sobrecarga global sobrecarga. No se permiten puntos intermedios como "todas las clases en el espacio de nombres X" o "todas las asignaciones en la unidad de traducción Y". Se requiere que los operadores específicos de clase sean static but pero en realidad no se requiere que los declare como estáticos they serán estáticos, ya sea que los declare explícitamente static o no. Oficialmente, los operadores globales devuelven mucha memoria alineada para que pueda ser utilizada para un objeto de cualquier tipo. Extraoficialmente, hay un pequeño margen de maniobra en uno consideración: si recibe una solicitud para un bloque pequeño (por ejemplo, 2 bytes), solo necesita proporcionar memoria alineada para un objeto de hasta ese tamaño, ya que intentar almacenar algo más grande allí conduciría a un comportamiento indefinido de todos modos.

Habiendo cubierto esos preliminares, volvamos a la pregunta original sobre por qué querrías sobrecargar estos operadores. En primer lugar, debo señalar que las razones para sobrecargar a los operadores globales tienden a ser sustancialmente diferentes de las razones para sobrecargar los operadores específicos de clase.

Ya que es más común, hablaré primero de los operadores específicos de clase. La razón principal para la gestión de memoria específica de clase es el rendimiento. Esto comúnmente viene en cualquiera (o ambos) de dos formas: o mejorar la velocidad, o reducir la fragmentación. La velocidad se mejora por el hecho de que el administrador de memoria solo se ocupará de bloques de un tamaño particular, por lo que puede devolver la dirección de cualquier bloque libre en lugar de que pasar cualquier tiempo comprobando si un bloque es lo suficientemente grande, dividiendo un bloque en dos si es demasiado grande, etc. La fragmentación se reduce (principalmente) de la misma manera for por ejemplo, pre-asignar un bloque lo suficientemente grande para N objetos da exactamente el espacio necesario para N objetos; asignar el valor de memoria de un objeto asignará exactamente el espacio para un objeto, y no un solo byte más.

Hay una variedad mucho mayor de razones para sobrecargar la memoria global operadores de gestión. Muchos de estos están orientados hacia la depuración o instrumentación, como el seguimiento de la memoria total necesaria por una aplicación (por ejemplo, en preparación para la portabilidad a un sistema embebido), o la depuración de problemas de memoria al mostrar desajustes entre la asignación y la liberación de memoria. Otra estrategia común es asignar memoria adicional antes y después de los límites de cada bloque solicitado, y escribir patrones únicos en esas áreas. Al final de la ejecución (y posiblemente otras veces también), esas áreas se examinan para ver si el código se ha escrito fuera de los límites asignados. Otra es intentar mejorar la facilidad de uso automatizando al menos algunos aspectos de la asignación o eliminación de memoria, como con un recolector de basura automatizado .

Un asignador global no predeterminado puede utilizarse también para mejorar el rendimiento. Un caso típico sería reemplazar un asignador predeterminado que era lento en general (por ejemplo, al menos algunas versiones de MS VC++ alrededor de las 4.x llamaría a las funciones del sistema HeapAlloc y HeapFree para cada operación de asignación/eliminación). Otra posibilidad que he visto en la práctica se produjo en los procesadores Intel cuando se utilizan las operaciones de SSE. Estos operan con datos de 128 bits. Si bien las operaciones funcionarán independientemente de la alineación, la velocidad mejora cuando los datos se alinean a los límites de 128 bits. Algunos compiladores (por ejemplo, MS VC++ de nuevo2) no necesariamente han forzado la alineación a ese límite más grande, por lo que incluso aunque el código usando el asignador predeterminado funcionaría, reemplazar la asignación podría proporcionar una mejora sustancial de la velocidad para esas operaciones.


  1. La mayoría de los requisitos están cubiertos en §3.7.3 y §18.4 del estándar C++ (o §3.7.4 y §18.6 en C++0x, al menos a partir de N3291).
  2. Me siento obligado a señalar que no tengo la intención de meterme en el compilador de Microsoft doubt Dudo que tenga un número inusual de tales problemas, pero sucede que lo uso mucho, así que tiendo a ser muy consciente de sus problemas.
 11
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-22 18:25:35

Muchas arquitecturas de computadoras requieren que los datos de tipos particulares se coloquen en memoria en tipos particulares de direcciones. Por ejemplo, una arquitectura puede requerir que los punteros se produzcan en direcciones que sean múltiplos de cuatro (es decir, estar alineados con cuatro bytes) o que los dobles se produzcan en direcciones que sean múltiplos de ocho (es decir, estar alineados con ocho bytes). El incumplimiento de estas restricciones puede dar lugar a excepciones de hardware en tiempo de ejecución. Otras arquitecturas son más indulgentes, y pueden permita que funcione aunque reduciendo el rendimiento.

Para aclarar: si una arquitectura requiere por ejemplo que double los datos estén alineados de ocho bytes, entonces no hay nada que optimizar. Cualquier tipo de asignación dinámica del tamaño apropiado (p. ej. malloc(size), operator new(size), operator new[](size), new char[size] donde se garantiza que size >= sizeof(double)) esté correctamente alineado. Si una implementación no hace esta garantía, no es conforme. Cambiando operator new para hacer "lo correcto" en ese caso sería un intento de "arreglar" la implementación, no una optimización.

Por otro lado, algunas arquitecturas permiten diferentes (o todos) tipos de alineación para uno o más tipos de datos, pero proporcionan diferentes garantías de rendimiento dependiendo de la alineación para esos mismos tipos. Una implementación puede entonces devolver memoria (de nuevo, asumiendo una solicitud de tamaño apropiado) que está sub-óptimamente alineada, y aún así ser conforme. De esto se trata el ejemplo.

 6
Author: Luc Danton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-22 16:12:16

El operador nuevo que viene con algunos compiladores no garantiza la alineación de ocho bytes para asignaciones dinámicas de dobles.

Citación, por favor. Normalmente, el operador nuevo predeterminado es solo un poco más complejo que un wrapper malloc, que, por estándar, devuelve memoria que está alineada adecuadamente para CUALQUIER tipo de datos que la arquitectura de destino soporte.

No es que esté diciendo que no hay buenas razones para sobrecargar nuevo y eliminar por el propio clase... y usted ha tocado varios legítimos aquí, pero el anterior no es uno de ellos.

 4
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-22 19:15:32

Relacionado con las estadísticas de uso: presupuestación por subsistema. Por ejemplo, en un juego basado en consola, es posible que desee reservar una fracción de memoria para la geometría del modelo 3D, algunos para texturas, algunos para sonidos, algunos para scripts de juegos, etc. Los asignadores personalizados pueden etiquetar cada asignación por subsistema y emitir una advertencia cuando se exceden los presupuestos individuales.

 3
Author: Russell Borogove,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-22 19:01:58

Lo utilicé para asignar objetos en una arena de memoria compartida específica. (Esto es similar a lo que @ Russell Borogove mencionó.)

Hace años desarrollé software para la CUEVA . Es un sistema multi-pared VR. Usaba una computadora para manejar cada proyector; 6 era el máximo (4 paredes, piso y techo) mientras que 3 era más común (2 paredes y el piso). Las máquinas se comunicaban a través de un hardware especial de memoria compartida.

Para apoyarlo, derivé de mi escena normal (no CUEVA) clases para usar un nuevo "nuevo" que pone la información de la escena directamente en la arena de memoria compartida. Luego pasé ese puntero a los renderizadores esclavos en las diferentes máquinas.

 3
Author: Andrew Dalke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-23 09:00:54

Parece que vale la pena repetir la lista de mi respuesta de "¿Alguna razón para sobrecargar global nuevo y eliminar?" aquí see vea esa respuesta (o de hecho otras respuestas a esa pregunta) para una discusión más detallada, referencias y otras razones. Estas razones generalmente se aplican a las sobrecargas del operador local, así como a las predeterminadas/globales, y a Cmalloc/calloc/realloc/free sobrecargas o ganchos también.

Sobrecargamos los operadores globales new y delete donde trabajo muchos razones:

  • pooling todas las asignaciones pequeñas decreases disminuye la sobrecarga, disminuye la fragmentación, puede aumentar el rendimiento para aplicaciones de asignación pequeña y pesada
  • enmarcar asignaciones con una vida conocida ignore ignorar todos los libres hasta el final de este período, luego liberar a todos ellos juntos (es cierto que hacemos esto más con sobrecargas de operadores locales que mundial)
  • alignment adjustment to to cacheline boundaries, etc
  • alloc fill helping ayudando a exponer el uso de variables no inicializadas
  • free fill helping ayudando a exponer el uso de la memoria previamente borrada
  • libre retardado increasing aumentando la efectividad del relleno libre, ocasionalmente aumentando el rendimiento
  • centinelas o postes de vallas helping ayudando a exponer sobrecostos de búfer, subestimaciones y el puntero salvaje ocasional
  • redireccionamiento asignaciones allocations para tener en cuenta NUMA, áreas de memoria especiales, o incluso para mantener sistemas separados en memoria (por ej. lenguajes de scripting incrustados o DSLS)
  • recolección de basura o limpieza useful nuevamente útil para aquellos lenguajes de scripting incrustados
  • verificación de montón can puede caminar a través de la estructura de datos de montón cada N allocs/frees para asegurarse de que todo se ve bien
  • contabilidad , incluido seguimiento de fugas y instantáneas de uso / estadísticas (pilas, edades de asignación, etc.)
 3
Author: leander,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:29:57