una tipos punteros objetos matriz matrices llenar dinámica dinamica crear cadenas arreglos arreglo c++ memory-management memory-leaks

c++ - tipos - punteros y matrices en c



¿Este código C++ causará una pérdida de memoria(matriz de casting nueva) (24)

He estado trabajando en algún código heredado de C ++ que usa estructuras de longitud variable (TAPI), donde el tamaño de la estructura dependerá de las cadenas de longitud variable. Las estructuras se asignan mediante casting array new así:

STRUCT* pStruct = (STRUCT*)new BYTE [sizeof(STRUCT) + nPaddingSize];

Más adelante, sin embargo, la memoria se libera mediante una llamada de delete :

delete pStruct;

¿Esta mezcla de array new [] y no array delete causa una pérdida de memoria o dependerá del compilador? ¿Sería mejor cambiar este código para usar malloc y free ?


@Matt Cruikshank

"Bueno, al experimentar con VS2005, no puedo obtener una filtración honesta de la eliminación escalar en la memoria que fue creada por el vector nuevo. Creo que el comportamiento del compilador es" indefinido "aquí, es la mejor defensa que puedo reunir".

No estoy de acuerdo con que sea un comportamiento del compilador o incluso un problema del compilador. La palabra clave ''nueva'' se compila y enlaza, como ha señalado, a las bibliotecas en tiempo de ejecución. Esas bibliotecas en tiempo de ejecución manejan las llamadas de administración de memoria al sistema operativo en una sintaxis consistente independiente del sistema operativo y esas bibliotecas en tiempo de ejecución son responsables de hacer malloc y el nuevo trabajo consistentemente entre sistemas operativos como Linux, Windows, Solaris, AIX, etc. . Esta es la razón por la que mencioné el argumento de portabilidad; un intento de demostrarle que el tiempo de ejecución tampoco administra la memoria.

El SO gestiona la memoria.

La interfaz libs en tiempo de ejecución para el sistema operativo ... En Windows, esta es la DLL del administrador de memoria virtual. Esta es la razón por la cual stdlib.h se implementa dentro de las bibliotecas GLIB-C y no en el código fuente del kernel de Linux; si GLIB-C se utiliza en otros sistemas operativos, es la implementación de cambios malloc para realizar las llamadas correctas del sistema operativo. En VS, Borland, etc., nunca encontrará bibliotecas que incluyan sus compiladores que realmente administren la memoria. Sin embargo, encontrará definiciones específicas de OS para malloc.

Como tenemos la fuente para Linux, puede ver cómo se implementa malloc allí. Verá que Malloc se implementa realmente en el compilador GCC que, a su vez, hace básicamente dos llamadas al sistema Linux en el kernel para asignar memoria. ¡Nunca, Malloc sí mismo, en realidad manejando la memoria!

Y no me lo quites. Lea el código fuente del SO Linux o puede ver lo que K & R dice al respecto ... Aquí hay un enlace en PDF al K & R en C.

http://www.oberon2005.ru/paper/kr_c.pdf

Ver al final de la página 149: "Las llamadas a malloc y free pueden ocurrir en cualquier orden; malloc solicita al sistema operativo que obtenga más memoria, según sea necesario. Estas rutinas ilustran algunas de las consideraciones involucradas en escribir código dependiente de la máquina en una máquina independiente manera, y también muestran una aplicación en la vida real de estructuras, uniones y typedef ".

"Sin embargo, debes admitir que es una práctica muy mala hacer lo que decía el cartel original".

Oh, no estoy en desacuerdo allí. Mi punto era que el código del póster original no era propicio para una pérdida de memoria. Eso es todo lo que estaba diciendo. No respondí el lado de las mejores prácticas de las cosas. Como el código llama a eliminar, la memoria se libera.

Estoy de acuerdo, en su defensa, si el código del póster original nunca salió o nunca llegó a la llamada de eliminación, que el código podría tener una pérdida de memoria, pero ya que afirma que más adelante verá que se llama a la eliminación. "Más tarde, sin embargo, la memoria se libera mediante una llamada de eliminación:"

Además, mi razón para responder como lo hice fue debido al comentario del OP "estructuras de longitud variable (TAPI), donde el tamaño de la estructura dependerá de las cadenas de longitud variable"

Ese comentario sonó como si estuviera cuestionando la naturaleza dinámica de las asignaciones en contra del lanzamiento y, consecuentemente, se preguntaba si eso causaría una pérdida de memoria. Estaba leyendo entre líneas si quieres;).


@Matt Cruikshank Deberías prestar atención y leer lo que escribí de nuevo porque nunca sugerí no llamar a delete [] y simplemente dejar que el sistema operativo lo limpiara. Y está equivocado sobre las bibliotecas de tiempo de ejecución de C ++ que administran el montón. Si ese fuera el caso, entonces C ++ no sería portátil como lo es hoy en día y el SO no limpiaría nunca una aplicación bloqueada. (reconociendo que hay tiempos de ejecución específicos del sistema operativo que hacen que C / C ++ parezca no portátil). Te reto a que encuentres stdlib.h en las fuentes de Linux de kernel.org. La nueva palabra clave en C ++ en realidad está hablando con las mismas rutinas de administración de memoria que malloc.

Las bibliotecas en tiempo de ejecución de C ++ hacen llamadas al sistema operativo y es el sistema operativo el que administra los montones. En parte, tiene razón en que las bibliotecas de tiempo de ejecución indican cuándo liberar la memoria, sin embargo, no caminan ninguna tabla de montón directamente. En otras palabras, el tiempo de ejecución al que se vincula no agrega código a su aplicación para recorrer montones para asignar o desasignar. Este es el caso en Windows, Linux, Solaris, AIX, etc ... También es la razón por la que no ajustará malloc en cualquier fuente de kernel de Linux ni encontrará stdlib.h en la fuente de Linux. Comprenda que estos sistemas operativos modernos tienen administradores de memoria virtual que complican las cosas un poco más.

¿Alguna vez se ha preguntado por qué puede llamar a malloc por 2G de RAM en una caja de 1G y aún obtener un puntero de memoria válido?

La administración de la memoria en los procesadores x86 se administra dentro del espacio Kernel usando tres tablas. PAM (tabla de asignación de páginas), PD (directorios de páginas) y PT (tablas de páginas). Esto es en el nivel de hardware del que estoy hablando. Una de las cosas que hace el administrador de memoria del sistema operativo, no su aplicación C ++, es averiguar cuánta memoria física está instalada en el buzón durante el inicio con la ayuda de las llamadas del BIOS. El sistema operativo también maneja excepciones como, por ejemplo, cuando intenta acceder a la memoria, su aplicación no tiene derechos. (Falla de protección general de GPF).

Puede ser que estemos diciendo lo mismo, Matt, pero creo que puedes confundir un poco la funcionalidad de Under Hood. Utilizo para mantener un compilador C / C ++ para vivir ...


@eric - Gracias por los comentarios. Sin embargo, sigues diciendo algo, eso me vuelve loco:

Esas bibliotecas en tiempo de ejecución manejan las llamadas de administración de memoria al sistema operativo en una sintaxis consistente independiente del sistema operativo y esas bibliotecas en tiempo de ejecución son responsables de hacer malloc y el nuevo trabajo consistentemente entre sistemas operativos como Linux, Windows, Solaris, AIX, etc. .

Esto no es verdad. El escritor del compilador proporciona la implementación de las bibliotecas std, por ejemplo, y son absolutamente libres de implementarlas de una manera dependiente del sistema operativo. Son libres, por ejemplo, para hacer una llamada gigante a malloc, y luego administrar la memoria dentro del bloque como lo deseen.

La compatibilidad se proporciona porque la API de std, etc. es la misma, no porque todas las bibliotecas en tiempo de ejecución giren y llamen exactamente a las mismas llamadas del sistema operativo.


@ericmayo - cripes. Bueno, al experimentar con VS2005, no puedo obtener una filtración honesta de la eliminación escalar en la memoria que fue creada por el vector nuevo. Supongo que el comportamiento del compilador es "indefinido" aquí, es la mejor defensa que puedo reunir.

Sin embargo, debes admitir que es una práctica muy mala hacer lo que decía el cartel original.

Si ese fuera el caso, entonces C ++ no sería portátil como lo es hoy en día y el SO no limpiaría nunca una aplicación bloqueada.

Sin embargo, esta lógica realmente no se sostiene. Mi afirmación es que el tiempo de ejecución de un compilador puede administrar la memoria dentro de los bloques de memoria que el sistema operativo le devuelve. Así es como funcionan la mayoría de las máquinas virtuales, por lo que su argumento en contra de la portabilidad en este caso no tiene mucho sentido.


Actualmente no puedo votar, pero la respuesta de slicedlime es preferible a la respuesta de Rob Walker , ya que el problema no tiene nada que ver con los asignadores o si el STRUCT tiene o no un destructor.

También tenga en cuenta que el código de ejemplo no necesariamente da como resultado una pérdida de memoria, es un comportamiento indefinido. Casi cualquier cosa podría pasar (de nada malo a un accidente muy, muy lejano).

El código de ejemplo da como resultado un comportamiento indefinido, simple y llano. La respuesta de slicedlime es directa y directa (con la advertencia de que la palabra "vector" debe cambiarse a "matriz" ya que los vectores son una cosa de STL).

Este tipo de cosas está cubierto bastante bien en las preguntas frecuentes de C ++ (Secciones 16.12, 16.13 y 16.14):

http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.12


Además de las excelentes respuestas anteriores, también me gustaría agregar:

Si tu código se ejecuta en Linux o si puedes compilarlo en Linux, te sugiero que lo ejecutes a través de Valgrind . Es una herramienta excelente, entre la miríada de advertencias útiles que produce, también le dirá cuándo asigna memoria como una matriz y luego la libera como una matriz (y viceversa).


Como se destaca en otras publicaciones:

1) Llamadas a la memoria asigna nueva / eliminar y puede llamar a constructores / destructores (C ++ ''03 5.3.4 / 5.3.5)

2) Mezclar versiones de matriz / no-matriz de new y delete es un comportamiento indefinido. (C ++ ''03 5.3.5 / 4)

Al observar la fuente, parece que alguien hizo una búsqueda y la reemplazó por malloc y free y lo anterior es el resultado. C ++ tiene un reemplazo directo para estas funciones, y es llamar a las funciones de asignación para new y delete directamente:

STRUCT* pStruct = (STRUCT*)::operator new (sizeof(STRUCT) + nPaddingSize); // ... pStruct->~STRUCT (); // Call STRUCT destructor ::operator delete (pStruct);

Si se debe llamar al constructor de STRUCT, entonces podría considerar asignar la memoria y luego usar la ubicación new :

BYTE * pByteData = new BYTE[sizeof(STRUCT) + nPaddingSize]; STRUCT * pStruct = new (pByteData) STRUCT (); // ... pStruct->~STRUCT (); delete[] pByteData;


Creo que no hay pérdida de memoria.

STRUCT* pStruct = (STRUCT*)new BYTE [sizeof(STRUCT) + nPaddingSize];

Esto se traduce en una llamada de asignación de memoria dentro del sistema operativo en la que se devuelve un puntero a esa memoria. En el momento en que se asigna la memoria, se conocerá el tamaño de sizeof(STRUCT) y el tamaño de nPaddingSize para cumplir con cualquier solicitud de asignación de memoria contra el sistema operativo subyacente.

Por lo tanto, la memoria que se asigna se "graba" en las tablas de asignación de memoria global del sistema operativo. Las tablas de memoria están indexadas por sus punteros. Por lo tanto, en la llamada correspondiente para eliminar, toda la memoria que se asignó originalmente es gratuita. (la fragmentación de la memoria también es un tema popular en este ámbito).

Verá, el compilador C / C ++ no está administrando la memoria, el sistema operativo subyacente es.

Estoy de acuerdo en que hay métodos más limpios, pero el OP sí dijo que esto era código heredado.

En resumen, no veo una fuga de memoria ya que la respuesta aceptada cree que hay una.


El comportamiento del código no está definido. Puede tener suerte (o no) y puede funcionar con su compilador, pero realmente ese no es el código correcto. Hay dos problemas con esto:

  1. La delete debe ser una matriz delete [] .
  2. La delete debe invocarse en un puntero al mismo tipo que el tipo asignado.

Entonces, para ser completamente correcto, quieres hacer algo como esto:

delete [] (BYTE*)(pStruct);


El estándar de C ++ establece claramente:

delete-expression: ::opt delete cast-expression ::opt delete [ ] cast-expression

La primera alternativa es para objetos que no son de matriz, y la segunda es para matrices. El operando debe tener un tipo de puntero, o un tipo de clase que tenga una sola función de conversión (12.3.2) a un tipo de puntero. El resultado tiene tipo vacío.

En la primera alternativa (eliminar objeto), el valor del operando de eliminación debe ser un puntero a un objeto que no sea array [...] Si no, el comportamiento no está definido.

El valor del operando en delete pStruct es un puntero a una matriz de char , independientemente de su tipo estático ( STRUCT* ). Por lo tanto, cualquier discusión sobre fugas de memoria es bastante inútil, porque el código está mal formado, y un compilador de C ++ no es necesario para producir un ejecutable sensible en este caso.

Podría perder memoria, no podría, o podría hacer cualquier cosa hasta dañar tu sistema. De hecho, una implementación en C ++ con la que probé tu código aborta la ejecución del programa en el punto de la expresión de eliminación.


Es una eliminación de matriz ([]) a la que se refiere, no una eliminación de vector. Un vector es std :: vector y se encarga de la eliminación de sus elementos.


Estás mezclando formas C y C ++ de hacer las cosas. ¿Por qué asignar más que el tamaño de un STRUCT? ¿Por qué no simplemente "nuevo STRUCT"? Si debe hacer esto, entonces podría ser más claro utilizar malloc y libre en este caso, ya que entonces usted u otros programadores podrían ser menos propensos a hacer suposiciones sobre los tipos y tamaños de los objetos asignados.


La respuesta de Rob Walker es buena.

Solo una pequeña adición, si no tiene ningún constructor y / o distructors, entonces básicamente necesita asignar y liberar una porción de memoria sin procesar, considere usar un par libre / malloc.


Len: el problema con eso es que pStruct es un STRUCT *, pero la memoria asignada es en realidad un BYTE [] de un tamaño desconocido. Entonces delete [] pStruct no desasignará toda la memoria asignada.


Los diversos usos posibles de las palabras clave new y delete parecen crear una gran cantidad de confusión. Siempre hay dos etapas para construir objetos dinámicos en C ++: la asignación de la memoria bruta y la construcción del nuevo objeto en el área de memoria asignada. En el otro lado de la vida del objeto está la destrucción del objeto y la desasignación de la ubicación de memoria donde residía el objeto.

Con frecuencia estos dos pasos son realizados por una sola declaración de C ++.

MyObject* ObjPtr = new MyObject; //... delete MyObject;

En lugar de lo anterior, puede usar el operator new funciones de asignación de memoria bruta de C ++ operator new y operator delete y la construcción explícita (a través de la ubicación new ) y la destrucción para realizar los pasos equivalentes.

void* MemoryPtr = ::operator new( sizeof(MyObject) ); MyObject* ObjPtr = new (MemoryPtr) MyObject; // ... ObjPtr->~MyObject(); ::operator delete( MemoryPtr );

Observe cómo no se trata de fundición, y solo se construye un tipo de objeto en el área de memoria asignada. Usar algo como new char[N] como una forma de asignar memoria bruta es técnicamente incorrecto ya que, lógicamente, los objetos char se crean en la memoria recientemente asignada. No sé de ninguna situación en la que no ''funcione'', pero difumina la distinción entre la asignación de memoria sin procesar y la creación de objetos, así que desaconsejo.

En este caso particular, no se obtiene ninguna ganancia al separar los dos pasos de la delete pero es necesario controlar manualmente la asignación inicial. El código anterior funciona en el escenario ''todo funciona'', pero perderá la memoria en bruto en el caso en que el constructor de MyObject arroje una excepción. Si bien esto podría ser capturado y resuelto con un manejador de excepciones en el punto de asignación, probablemente sea mejor proporcionar un operador personalizado nuevo para que la construcción completa pueda ser manejada por una nueva expresión de ubicación.

class MyObject { void* operator new( std::size_t rqsize, std::size_t padding ) { return ::operator new( rqsize + padding ); } // Usual (non-placement) delete // We need to define this as our placement operator delete // function happens to have one of the allowed signatures for // a non-placement operator delete void operator delete( void* p ) { ::operator delete( p ); } // Placement operator delete void operator delete( void* p, std::size_t ) { ::operator delete( p ); } };

Hay un par de puntos sutiles aquí. Definimos una ubicación de clases nueva para que podamos asignar suficiente memoria para la instancia de la clase más algún margen de relleno que se pueda especificar por el usuario. Debido a que hacemos esto, debemos proporcionar una eliminación de ubicación coincidente para que, si la asignación de memoria se realiza correctamente pero la construcción falla, la memoria asignada se desasigna automáticamente. Lamentablemente, la firma de nuestra eliminación de ubicación coincide con una de las dos firmas permitidas para la eliminación sin ubicación, por lo que debemos proporcionar la otra forma de eliminación sin ubicación para que nuestra eliminación de ubicación real se trate como una eliminación de ubicación. (Podríamos haber solucionado esto agregando un parámetro ficticio adicional tanto para nuestra ubicación nueva como para la eliminación de ubicación, pero esto habría requerido trabajo adicional en todos los sitios de llamadas).

// Called in one step like so: MyObject* ObjectPtr = new (padding) MyObject;

Usando una única expresión nueva, ahora tenemos garantizado que la memoria no se fugará si se lanza alguna parte de la nueva expresión.

En el otro extremo de la duración del objeto, porque definimos la eliminación del operador (incluso si no lo hubiéramos hecho, la memoria para el objeto originalmente provino del operador global new en cualquier caso), la siguiente es la forma correcta de destruir el objeto creado dinámicamente .

delete ObjectPtr;

¡Resumen!

  1. ¡No busques yesos! operator new y operación de operator delete con memoria sin procesar, la colocación nueva puede construir objetos en la memoria sin formato. Un lanzamiento explícito desde un void* a un puntero de objeto suele ser un signo de algo lógicamente incorrecto, incluso si "simplemente funciona".

  2. Hemos ignorado completamente new [] y delete []. Estos objetos de tamaño variable no funcionarán en matrices en ningún caso.

  3. La colocación nueva permite que una nueva expresión no se filtre, la nueva expresión aún se evalúa como un puntero a un objeto que necesita destrucción y memoria que necesita desasignación. El uso de algún tipo de puntero inteligente puede ayudar a prevenir otros tipos de fugas. En el lado positivo, hemos dejado que una delete simple sea la forma correcta de hacerlo, por lo que la mayoría de los punteros inteligentes estándar funcionarán.


Personalmente creo que sería mejor usar std::vector para administrar su memoria, por lo que no necesita la delete .

std::vector<BYTE> backing(sizeof(STRUCT) + nPaddingSize); STRUCT* pStruct = (STRUCT*)(&backing[0]);

Una vez que el respaldo deja alcance, su pStruct ya no es válido.

O bien, puedes usar:

boost::scoped_array<BYTE> backing(new BYTE[sizeof(STRUCT) + nPaddingSize]); STRUCT* pStruct = (STRUCT*)backing.get();

O bien, boost::shared_array si necesita mover la propiedad.


Podrías devolver a un BYTE * y eliminar:

delete[] (BYTE*)pStruct;


Sí que puede, dado que su asignación con new [] pero desasignación con delelte, yes malloc / free es más seguro aquí, pero en c ++ no debe usarlos ya que no manejarán (de) constructores.

También su código llamará al deconstructor, pero no al constructor. Para algunas estructuras esto puede causar una pérdida de memoria (si el constructor asignó más memoria, por ejemplo, para una cadena)

Sería mejor hacerlo correctamente, ya que esto también llamará correctamente a los constructores y deconstructores

STRUCT* pStruct = new STRUCT; ... delete pStruct;


Sí, causará una pérdida de memoria.

Ver esto, excepto de C ++ Gotchas: http://www.informit.com/articles/article.aspx?p=30642 por qué.

Raymond Chen tiene una explicación de cómo el vector new y el delete difieren de las versiones escalares bajo las cubiertas para el compilador de Microsoft ... Aquí: http://blogs.msdn.com/oldnewthing/archive/2004/02/03/66660. aspx

En mi humilde opinión, debe corregir la eliminación para:

delete [] pStruct;

en lugar de cambiar a malloc / free , solo porque es un cambio más simple sin cometer errores;)

Y, por supuesto, cuanto más fácil de hacer el cambio que muestro arriba es incorrecto debido a la conversión en la asignación original, debería ser

delete [] reinterpret_cast<BYTE *>(pStruct);

entonces, supongo que probablemente sea tan fácil cambiar a malloc / free después de todo;)


Si realmente debe hacer este tipo de cosas, probablemente debería llamar al operador new directamente:

STRUCT* pStruct = operator new(sizeof(STRUCT) + nPaddingSize);

Creo que llamarlo así evita llamar a constructores / destructores.


Siempre es mejor mantener la adquisición / liberación de cualquier recurso lo más equilibrada posible. Aunque fugas o no es difícil de decir en este caso. Depende de la implementación del compilador de la asignación de vector (de).

BYTE * pBytes = new BYTE [sizeof(STRUCT) + nPaddingSize]; STRUCT* pStruct = reinterpret_cast< STRUCT* > ( pBytes ) ; // do stuff with pStruct delete [] pBytes ;


Técnicamente, creo que podría causar un problema con asignadores no coincidentes, aunque en la práctica no conozco ningún compilador que no haga lo correcto con este ejemplo.

Más importante aún, si STRUCT tuviera (o se le diera) un destructor, invocaría al destructor sin haber invocado el constructor correspondiente.

Por supuesto, si usted sabe de dónde viene pStruct, ¿por qué no simplemente lanzarlo en eliminar para que coincida con la asignación?

delete [] (BYTE*) pStruct;


Use operador nuevo y elimine:

struct STRUCT { void *operator new (size_t) { return new char [sizeof(STRUCT) + nPaddingSize]; } void operator delete (void *memory) { delete [] reinterpret_cast <char *> (memory); } }; void main() { STRUCT *s = new STRUCT; delete s; }


ericmayo.myopenid.com está tan equivocado, que alguien con suficiente reputación debería rechazarlo.

Las bibliotecas de tiempo de ejecución C o C ++ están administrando el montón que el Sistema operativo le asigna en bloques, algo así como lo indica, Eric. Pero es responsabilidad del desarrollador indicarle al compilador qué llamadas de tiempo de ejecución deben realizarse para liberar memoria y posiblemente destruir los objetos que están allí. Vector delete (aka delete []) es necesario en este caso, para que el tiempo de ejecución de C ++ deje el montón en un estado válido. El hecho de que cuando termina el PROCESO, el sistema operativo es lo suficientemente inteligente como para desasignar los bloques de memoria subyacentes no es algo en lo que los desarrolladores deben confiar. Esto sería como nunca llamar eliminar en absoluto.