En C ++, cuánto tiempo se dedica al programador a administrar la memoria

39

Las personas que están acostumbradas a los lenguajes recolectados de basura a menudo tienen miedo de la administración de memoria de C ++. Hay herramientas, como auto_ptry shared_ptrque manejarán muchas de las tareas de administración de memoria por usted. Muchas bibliotecas de C ++ son anteriores a esas herramientas y tienen su propia forma de manejar las tareas de administración de memoria.

¿Cuánto tiempo pasas en tareas de administración de memoria?

Sospecho que depende en gran medida del conjunto de bibliotecas que utilice, por lo tanto, indique a qué se aplica su respuesta y si la mejoran o empeoran.

Sean McMillan
fuente
1
No tanto, realmente ... Especialmente con C ++ 0x, referencias y STL. Incluso puede escribir código sin administración de memoria en absoluto.
Codificador
9
En general: no tanto si tienes experiencia. Mucho si eres novato en C ++ (-> generalmente buscando pérdidas de memoria / recursos).
MaR
1
Encuentro la verdadera pregunta, en estos días, más sobre perseguir referencias obsoletas. Y generalmente es bastante evidente cada vez, solo molesto que no haya sido capturado antes: p
Matthieu M.
Sé que esto es antiguo, pero la administración de memoria IMO es una parte integral de ser un buen programador. Las abstracciones como los contenedores STL son agradables, pero la ignorancia de la memoria está en contra de la idea misma de la computación misma. También se podría preguntar cómo se puede eliminar la manipulación algebraica, la lógica y los bucles del arsenal del programador.
imallett
¿Qué tal "cuánto tiempo se usa para depurar la gestión de memoria que salió mal?" En sí, la administración de memoria es posible y no tan difícil en C ++. El hecho es: configurarlo es una tarea precisa, y es muy propenso a los errores. Cuando la cagas, es posible que ni siquiera te des cuenta, y rastrear hasta viejos errores con comportamientos erráticos que se acumularon con el tiempo, es el sumidero en tiempo real del que deberías tener miedo. Es por eso que los lenguajes modernos no recolectados de basura (estoy pensando en el óxido) trasladaron una gran responsabilidad de verificar los errores típicos al compilador.
ZJR

Respuestas:

54

El C ++ moderno hace que no se preocupe por la administración de la memoria hasta que tenga que hacerlo, es decir, hasta que necesite organizar su memoria a mano, principalmente para fines de optimización, o si el contexto lo obliga a hacerlo (piense en hardware de restricciones grandes). He escrito juegos completos sin manipular la memoria en bruto, solo me preocupa el uso de contenedores que son la herramienta adecuada para el trabajo, como en cualquier idioma.

Por lo tanto, depende del proyecto, pero la mayoría de las veces no es la gestión de la memoria lo que tiene que manejar, sino solo la vida útil del objeto. Eso se resuelve usando punteros inteligentes , que es una herramienta idiomática de C ++ resultante de RAII .

Una vez que comprenda RAII , la administración de memoria no será un problema.

Luego, cuando necesite acceder a la memoria sin procesar, lo hará en un código muy específico, localizado e identificable, como en las implementaciones de objetos de grupo, no "en todas partes".

Fuera de este tipo de código, no necesitará manipular la memoria, solo objetos de por vida.

La parte "difícil" es entender RAII.

Klaim
fuente
10
Totalmente cierto. En los últimos 5 años, solo he escrito "eliminar" cuando trabajo con código heredado.
drxzcl
3
Trabajo en un entorno embebido con restricciones de gran tamaño de pila. Por genial que sea RAII, no funciona bien si el espacio de pila es escaso. Así que vuelve a la microgestión del puntero.
bastibe
1
@nikie Uso los punteros inteligentes de las bibliotecas en el código que manipula su API, luego uso punteros inteligentes estándar o de refuerzo en el código específico de mi aplicación (si soy yo quien decide sobre eso). Si puede aislar el código de la biblioteca en algunos módulos que resumen cómo se usan en su aplicación, entonces evitará la contaminación API de las dependencias.
Klaim
12
@Paperflyer: RAII no ocupará más espacio de pila que deletemanualmente, a menos que tenga una implementación de mierda.
DeadMG
2
@Paperflyer: el puntero inteligente en el montón ocupa el mismo espacio; la diferencia es que el compilador inserta el código de desasignación de recursos en todas las salidas de una función. Y puesto que este se utiliza tan ampliamente, esto es por lo general bien optimizado (por ejemplo, plegado múltiples salidas juntos de una manera que no se puede - no se puede poner el código después de una return)
MSalters
32

La administración de memoria se usa para asustar a los niños, pero es solo un tipo de recurso que un programador debe cuidar. Piense en los identificadores de archivos, las conexiones de red y otros recursos que obtiene del sistema operativo.

Los lenguajes que admiten la recolección de basura generalmente no solo ignoran la existencia de estos recursos, sino que también hacen que sea más difícil manejarlos adecuadamente al no proporcionar un destructor.

Entonces, en resumen, sugeriría que no se dedique gran parte del tiempo de un desarrollador de C ++ a preocuparse por la administración de la memoria. Como indica la respuesta de klaim , una vez que manejas RAII, el resto es solo reflejo.

Dysaster
fuente
3
Especialmente me encanta cómo HttpWebRequest.GetResponse filtra los identificadores y comienza a fallar en los lenguajes GC. GC es genial, hasta que comienza a succionar porque los recursos aún se escapan. msdn.microsoft.com/en-us/library/… Consulte "Precaución".
Codificador
66
+1 para ver la memoria como recurso. Código heredado o no, cuántas veces necesitamos gritar en voz alta: la administración de memoria es una habilidad y no una maldición .
aquaherd
44
@Coder No estoy seguro si sigo ... GC apesta porque de todos modos es posible abusar de los recursos ...? Creo que C # hace un buen trabajo al proporcionar liberación determinista de recursos usando IDisposable ...
Max
8
@Max: porque si se trata de basura recolectada, entonces espero no preocuparme por los estúpidos recursos mediante el uso de IDisposables personalizados Los recursos abandonaron el alcance, eso es todo, deben limpiarse. En realidad, sin embargo, todavía tengo que pensar y adivinar cuáles se filtrarán y cuáles no. Supera cualquier razón para usar el lenguaje GC en primer lugar.
Codificador
55
@deadalnix Tienen la finalizeconstrucción. Sin embargo, no sabe cuándo se llamará. ¿Será antes de que te quedes sin sockets u objetos de WebResponse? Encontrará muchos artículos que le dicen que no debe confiar finalize, con buenas razones.
Dysaster
13

Casi ninguno. Incluso las tecnologías antiguas como COM, puede escribir eliminadores personalizados para los punteros estándar que los convertirán en muy poco tiempo. Por ejemplo, std::unique_ptrse puede convertir para mantener de forma exclusiva una referencia COM con cinco líneas de un eliminador personalizado. Incluso si tiene que escribir manualmente su propio manejador de recursos, la prevalencia de conocimientos como SRP y copiar y cambiar hace que sea relativamente fácil escribir una clase de gestión de recursos para usar para siempre.

La realidad es que todo lo compartido, único y sin propiedad se entrega con su compilador C ++ 11, y solo tiene que escribir pequeños adaptadores para que funcionen incluso con código antiguo.

DeadMG
fuente
1
¿Cuánta habilidad con C ++ tienes que tener para a) escribir un eliminador personalizado b) saber que un eliminador personalizado es lo que necesitas? Pregunto porque parece fácil elegir un nuevo lenguaje GC'd y acercarse a la corrección sin saberlo todo. ¿También es fácil acertar en C ++?
Sean McMillan
1
@SeanMcMillan: los eliminadores personalizados son triviales para escribir e implementar, el COM que mencioné es de cinco líneas para todos los tipos de COM, y cualquiera con una formación básica en C ++ moderno debería estar familiarizado con ellos. No puede elegir un lenguaje GCed, porque sorpresa: el GC no recopilará objetos COM. O asas de archivo. O memoria obtenida de otros sistemas. O conexiones de bases de datos. RAII hará todas esas cosas.
DeadMG
2
Al decir "Elija un lenguaje GC'd", me refería a que había saltado entre Java / C # / Ruby / Perl / Javascript / Python, y todos tienen el mismo estilo de administración de recursos: la memoria es principalmente automática y todo lo demás , tienes que manejarlo. Me parece que está diciendo que las herramientas de administración de C ++ le permiten administrar los identificadores de archivos / conexiones db / etc. de la misma manera que la memoria, y que es relativamente sencillo una vez que lo aprende. No cirugía cerebral. ¿Entiendo correctamente?
Sean McMillan
3
@SeanMcMillan: Sí, eso es exactamente correcto, y no es complejo.
DeadMG
11

Cuando era un programador de C ++ (hace mucho tiempo), pasé mucho tiempo preocupándome por el error de administración de memoria al tratar de solucionar errores difíciles de reproducir .

Con el módem C ++, la administración de memoria es un problema mucho menor, pero ¿puede confiar en que todos los miembros de un gran equipo lo hagan bien? ¿Cuál es el costo / tiempo de:

  • Capacitación (no muchos programadores llegan con una buena comprensión de los problemas)
  • Revisiones de código para encontrar problemas de administración de memoria
  • Depuración de problemas de administración de memoria
  • Siempre teniendo en cuenta que un error en una parte de la aplicación puede deberse a un problema de administración de memoria en una parte no relacionada de la aplicación .

Por lo tanto, no se trata solo del tiempo que se dedica a “ hacer ”, sino que se trata más de un problema en grandes proyectos.

Ian
fuente
2
Creo que algunos proyectos de C ++ han perdido la esperanza de arreglar alguna de sus pérdidas de memoria debido a un código mal escrito. Va a suceder un código incorrecto, y cuando lo hace, puede tomar mucho tiempo de otras personas también.
Jeremy
@Jeremy, descubrí que cuando me mudé de C ++ a C #, todavía había tanto código mal escrito (si no más), pero al menos era mucho más fácil encontrar la parte del programa que tenía un error determinado.
Ian
1
Sí, es por eso que la mayoría de las tiendas se han mudado a Java o .NET. La recolección de basura mitiga el daño inevitable del mal código.
Jeremy
1
Por extraño que parezca, no tenemos esos problemas.
David Thornley, el
1
@DavidThornley, creo que gran parte del problema fue escribir código UI en C ++, actualmente la mayoría del código C ++ que veo no es UI
Ian
2

Utilizo mucho las bibliotecas boost y TR1, y hacen que la administración de memoria en sentido estricto (nuevo / eliminar) no sea un problema. Por otro lado, la asignación de memoria en C ++ no es barata, y uno debe prestar atención a dónde se crean estos elegantes punteros compartidos. Terminas usando mucho espacios de trabajo o trabajando con memoria basada en pila. En general, diría que es principalmente un problema de diseño, no un problema de implementación.

cuant_dev
fuente
2

¿Cuánto tiempo lleva como cliente? muy poco, una vez que te acostumbras. cuando un contenedor gestiona la vida útil y las referencias, es realmente muy fácil. OMI, es mucho más simple que el conteo manual de referencias, y es prácticamente transparente si considera el contenedor que usa como documentación que el compilador convenientemente le impide realizar transferencias de propiedad no válidas en un sistema seguro de tipografía bien diseñado.

La mayor parte del tiempo que paso (como cliente) lo paso conteniendo tipos de otras API, por lo que funcionan bien dentro del contexto de sus programas. ejemplo: esta es mi contenedor ThirdPartyFont, y es compatible con estas características, y la destrucción implementos de esta manera, y la referencia a contar de esta manera, y la copia de esta manera, y ... . Muchas de esas construcciones deben estar en su lugar, y a menudo es el lugar lógico para colocarlas. si desea incluir eso como tiempo o no depende de su definición (la implementación debe existir al interactuar con estas API, de todos modos, ¿verdad?).

después de eso, deberá tener en cuenta la memoria y la propiedad. en un sistema de nivel inferior, eso es bueno y necesario, pero puede llevar algo de tiempo y andamiaje implementar cómo debe mover las cosas. No lo veo como un dolor ya que este es un requisito de un sistema de nivel inferior. propiedad, control y responsabilidad son evidentes.

así que podemos orientarlo hacia apis basadas en c que usan tipos opacos: nuestros contenedores nos permiten abstraer todos los pequeños detalles de implementación de la gestión de la vida útil y la copia de esos tipos opacos, lo que en última instancia hace que la gestión de recursos sea muy simple y ahorre tiempo, defectos, y reduce implementaciones.

es realmente muy simple usarlos: el problema (proveniente de GC) es que ahora debe considerar la vida útil de sus recursos. Si se equivoca, puede llevar mucho tiempo resolverlo. El aprendizaje y la integración de la gestión explícita de por vida es comprensiblemente complejo en comparación (no para todas las personas), ese es el verdadero obstáculo. una vez que se sienta cómodo controlando vidas y utilizando buenas soluciones, es realmente muy fácil administrar las vidas de los recursos. No es una parte importante de mi día (a menos que se haya introducido un error difícil).

Si no está utilizando contenedores (puntero automático / compartido), entonces solo está pidiendo dolor.

He implementado mis propias bibliotecas. Me lleva tiempo implementar esas cosas, pero la mayoría de las personas las reutilizan (lo que suele ser una buena idea).

justin
fuente
1

¿Te refieres a tener que liberar memoria manualmente, cerrar archivos, cosas de este tipo? Si es así, diría que el mínimo y típicamente menos que la mayoría de los otros idiomas que he usado, especialmente si generalizamos eso no solo a la "administración de memoria" sino a la "administración de recursos". En ese sentido, creo que C ++ requiere menos administración de recursos manual que, por ejemplo, Java o C #.

Se debe principalmente a los destructores que automatizan la destrucción del recurso (memoria o no). Por lo general, el único momento en que tengo que liberar / destruir un recurso manualmente en C ++ es si estoy implementando una estructura de datos a nivel de vlow (algo que la mayoría de la gente no necesita hacer) o usando una API de C donde solo paso un poco de tiempo envolviendo el recurso C que necesita ser liberado / destruido / cerrado manualmente en un contenedor C ++ compatible con RAII.

Por supuesto, si un usuario solicita cerrar una imagen en un software de edición de imágenes, tengo que eliminar la imagen de una colección o algo así. Pero, con suerte, eso no cuenta como gestión de "memoria" o "recurso" de un tipo que importa en este contexto, ya que eso es bastante necesario en cualquier idioma si desea liberar la memoria asociada con esa imagen en ese momento. Pero de nuevo, todo lo que tiene que hacer es eliminar la imagen de la colección y el destructor de imágenes se encargará del resto.

Mientras tanto, si me comparo con, por ejemplo, Java o C #, a menudo encuentras personas que tienen que cerrar archivos manualmente allí, desconectar manualmente los sockets, establecer referencias de objeto en nulo para permitir que se recojan basura, etc. Hay mucha más memoria manual y gestión de recursos en esos idiomas si me preguntas. En C ++, a menudo ni siquiera necesita unlockun mutex manualmente, ya que el casillero mutex lo hará automáticamente cuando el mutex salga del alcance. Por ejemplo, nunca debería tener que hacer cosas como esta en C ++:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

No hay necesidad de hacer cosas como cerrar archivos manualmente en C ++. Terminan cerrándose automáticamente en el instante en que salen del alcance, ya sea que se salgan del alcance como resultado o como rutas de ejecución normales o excepcionales. Algo similar para recursos relacionados con la memoria como std::vector. Tal código como el file.Close()anterior a menudo estaría mal visto, ya que, especialmente en el contexto de un finallybloque, sugiere que el recurso local debe liberarse manualmente cuando toda la mentalidad en torno a C ++ es automatizar eso.

En términos de gestión de memoria manual, diría que C requiere el máximo, Java / C # una cantidad media y C ++ el mínimo entre estos. Hay muchas razones para ser un poco tímido al usar C ++ ya que es un lenguaje muy difícil de dominar, pero la administración de memoria no debería ser una de ellas. Por el contrario, creo que es uno de los idiomas más fáciles que existen en este aspecto.

Por supuesto, C ++ le permite comenzar a asignar memoria manualmente e invocar operator delete/delete[]para liberar memoria manualmente. También le permite usar funciones C como mallocyfree. Pero esas son prácticas de codificación de estilo antiguo que creo que se volvieron obsoletas mucho antes de que las personas den crédito, ya que Stroustrup defendía la RAII incluso antes de que acuñara el término desde el principio. Así que ni siquiera creo que sea justo decir que "C ++ moderno" automatiza la gestión de recursos, porque se suponía que ese era el propósito todo el tiempo. Prácticamente no puede obtener seguridad de excepción de lo contrario. Es solo que muchos desarrolladores equivocados a principios de los 90 intentaron usar C ++ como C con objetos, a menudo ignorando por completo el manejo de excepciones, y nunca se suponía que se usara de esa manera. Si usa C ++ de la forma en que prácticamente siempre se pretendía usar, entonces la administración de memoria está totalmente automatizada y, en general, no es algo con lo que tenga que lidiar manualmente (o debería lidiar) en absoluto.


fuente
1
Java moderno tiene "prueba con recursos" que elimina todo ese código desordenado en el bloque finalmente. Rara vez es necesario tener un bloqueo finalmente. Parece que los diseñadores han copiado el concepto RAII.
kiwiron
0

Depende de los líderes técnicos superiores en el equipo. En algunas empresas (incluida la mía), no existe un concepto llamado puntero inteligente. Se considera elegante. Por lo tanto, la gente simplemente pone eliminaciones por todas partes y hay una unidad para la reparación de pérdidas de memoria por cada 2 meses. Nueva ola de declaraciones de eliminación llegan a todas partes. Entonces, depende de la compañía y del tipo de personas que trabajan allí.

Jagannath
fuente
1
¿Hay algo en su entorno que le impide usar auto_ptry amigos?
Sean McMillan
2
Parece que su empresa no escribe código C ++, está escribiendo C.
gbjbaanb