c++ - resueltos - memoria dinamica
¿La asignación de memoria dinámica difiere en C y C++ en implementaciones populares? (5)
Aquí está la implementación utilizada por g++ 4.6.1
:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)
{
void *p;
/* malloc (0) is unpredictable; avoid it. */
if (sz == 0)
sz = 1;
p = (void *) malloc (sz);
while (p == 0)
{
new_handler handler = __new_handler;
if (! handler)
#ifdef __EXCEPTIONS
throw bad_alloc();
#else
std::abort();
#endif
handler ();
p = (void *) malloc (sz);
}
return p;
}
Esto se encuentra en libstdc++-v3/libsupc++/new_op.cc
dentro de la distro fuente g ++.
Como puede ver, es un envoltorio bastante delgado alrededor de malloc
.
editar En muchos sistemas, es posible ajustar el comportamiento de malloc
, normalmente llamando mallopt
o estableciendo variables de entorno. Aquí hay un article analiza algunas características disponibles en Linux.
Según Wikipedia , las versiones 2.3+ de glibc
usan una versión modificada del asignador llamado ptmalloc
, que a su vez es un derivado de dlmalloc
diseñado por Doug Lea . Curiosamente, en un artículo sobre dlmalloc
Doug Lea da la siguiente perspectiva (el énfasis es mío):
Escribí la primera versión del asignador después de escribir algunos programas en C ++ que se basaban casi exclusivamente en la asignación de memoria dinámica. Descubrí que funcionaban mucho más despacio y / o con un consumo de memoria total mucho mayor de lo que esperaba. Esto se debió a las características de los asignadores de memoria en los sistemas en los que me estaba ejecutando (principalmente las versiones actuales de SunOs y BSD). Para contrarrestar esto, al principio escribí un número de asignadores de propósito especial en C ++, normalmente sobrecargando operador nuevo para varias clases. Algunos de estos se describen en un artículo sobre técnicas de asignación de C ++ que se adaptó en el artículo del Informe C ++ de 1989. Algunas técnicas de asignación de almacenamiento para clases de contenedores.
Sin embargo, pronto me di cuenta de que la creación de un asignador especial para cada clase nueva que solía ser dinámicamente asignada y muy utilizada no era una buena estrategia para crear clases de clases de soporte de programación de uso general que estaba escribiendo en ese momento. (De 1986 a 1991, fui el principal autor de libg ++, la biblioteca GNU C ++.) Se necesitaba una solución más amplia: escribir un asignador que fuera lo suficientemente bueno bajo cargas normales de C ++ y C para que los programadores no tuvieran la tentación de hacerlo. escriba asignadores de propósito especial excepto en condiciones muy especiales.
Este artículo presenta una descripción de algunos de los principales objetivos de diseño, algoritmos y consideraciones de implementación para este asignador.
En lo que respecta a los estándares de idioma respectivos, C ofrece asignación de memoria dinámica solo a través de la familia malloc()
, mientras que en C ++ la forma más común de asignación la realiza ::operator new()
. El malloc de estilo C también está disponible en C ++, y muchos ejemplos del "primer asignador de bebés" lo utilizan como su función de asignación central, pero tengo curiosidad de cómo los compiladores contemporáneos implementan el operador de producción actual: nuevo.
¿Es solo una capa delgada alrededor de malloc()
, o se implementa de manera fundamentalmente diferente a causa del comportamiento de asignación de memoria bastante diferente de un programa típico de C ++ en comparación con un programa típico de C?
[ Editar: Creo que la principal diferencia generalmente se describe de la siguiente manera: el programa de CA tiene asignaciones menores, más grandes y de larga duración, mientras que un programa de C ++ tiene muchas asignaciones pequeñas y de corta vida. Siéntete libre de sonar si eso está equivocado, pero parece que uno se beneficiaría de tener esto en cuenta.]
Para un compilador como GCC, sería fácil simplemente tener una única implementación de asignación central y usarla para todos los idiomas relevantes, por lo que me pregunto si hay diferencias en los detalles que intentan optimizar el rendimiento de asignación resultante en cada idioma.
Actualización: ¡ Gracias por todas las excelentes respuestas! Parece que en GCC esto está completamente resuelto por ptmalloc , y que MSVC también usa malloc
en el núcleo. ¿Alguien sabe cómo se implementa MSVC-malloc?
El nuevo operador glibc es un envoltorio delgado alrededor de malloc. Y glibc malloc usa diferentes estrategias para diferentes solicitudes de asignación de tamaño. Puede ver la implementación, o al menos los comentarios here .
Aquí hay un extracto de los comentarios en malloc.c:
/*
47 This is not the fastest, most space-conserving, most portable, or
48 most tunable malloc ever written. However it is among the fastest
49 while also being among the most space-conserving, portable and tunable.
50 Consistent balance across these factors results in a good general-purpose
51 allocator for malloc-intensive programs.
52
53 The main properties of the algorithms are:
54 * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
55 with ties normally decided via FIFO (i.e. least recently used).
56 * For small (<= 64 bytes by default) requests, it is a caching
57 allocator, that maintains pools of quickly recycled chunks.
58 * In between, and for combinations of large and small requests, it does
59 the best it can trying to meet both goals at once.
60 * For very large requests (>= 128KB by default), it relies on system
61 memory mapping facilities, if supported.
*/
En Visual C ++, entrar en una new
expresión me lleva a este fragmento en new.cpp
:
#include <cstdlib>
#include <new>
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
Entonces, el nuevo VC ++ también envuelve la llamada malloc()
.
En la mayoría de las implementaciones, el operator new()
simplemente llama a malloc()
. De hecho, incluso The Standard sugiere que como estrategia predeterminada . Por supuesto, puede implementar su propio operator new
, generalmente para una clase si desea un mejor rendimiento, pero el valor predeterminado es simplemente llamar a malloc()
.
No es una cuestión de rendimiento: pA = new A
tiene un efecto secundario diferente de pA = (A*)malloc(sizeof(A));
En el segundo, el constructor de A no es llamado. Para llegar a un mismo efecto deberías hacer
pA = (A*)malloc(sizeof(A));
new(pA)A();
donde nuevo es la "colocación nueva" ...
void* operator new(size_t sz, void* place)
{ return place; }