Pregunta

He escrito un archivo de biblioteca simple con una función para leer las líneas de un archivo de cualquier tamaño. La función se llama mediante el paso en un tampón y tamaño de la pila-asignado, pero si la línea es demasiado grande, un tampón especial heap-asignado se inicializa y se utiliza para pasar de nuevo una línea más grande.

Este búfer de memoria dinámica asignada es función de ámbito y estático declarado, inicializado a NULL al principio del curso. He escrito en algunos controles en el comienzo de la función, para comprobar si el búfer de memoria dinámica no es nulo; si este es el caso, entonces la lectura línea anterior era demasiado largo. Naturalmente, me libero del búfer de pila y la puse de nuevo a NULL, el pensamiento de que es probable que sólo necesite la próxima lectura para llenar el búfer de pila asignado (que debe ser muy raro ver líneas más de 1 MB de largo, incluso en nuestra aplicación!).

He repasado el código y lo probó con bastante profundidad, tanto mediante la lectura cuidadosamente y mediante la ejecución de algunas pruebas. Estoy razonablemente seguro de que la siguiente se mantiene invariable:

  • El búfer de pila será nulo (y no se escape cualquier memoria) en retorno de la función si el búfer de pila es todo lo que se necesita.
  • Si el búfer de memoria dinámica no es nulo, porque se necesitaba, que será liberado en la siguiente llamada de función (y posiblemente volver a utilizar si es necesario en esa línea siguiente).

Pero he pensado en un problema potencial: Si la última línea de un archivo es demasiado largo, a continuación, ya que la función es de suponer que no se llama de nuevo, no estoy seguro de tener alguna manera de liberar el montón buffer-- es la función de ámbito, después de todo.

Así que mi pregunta es, ¿cómo hago para liberar memoria asignada dinámicamente en función de un puntero estática con ámbito, a ser posible sin llamar a la función de nuevo? (E idealmente sin que sea una variable global, ya sea!)

Código disponibles bajo petición. (Yo no tengo acceso ahora, lo siento. Y espero que la pregunta es suficientemente general y bien explicado para que no sea necesario, pero por todos los medios no dude en desengañar de esa noción!)


EDIT: me siento debo añadir un par de notas sobre el uso de la función

.

Esta función particular se utiliza en forma de líneas que se leen en serie desde un archivo, y luego se copia inmediatamente en estructuras vaina, una línea por struct. Los que se crean en el montón como se lee el archivo, y cada una de esas estructuras tiene un puntero char que contiene (a versión limpia de) una línea del archivo. A fin de que éstas persistan, una copia ya se tiene que ocurrir. (Ese fue uno de los grandes argumentos en contra criados en muchos de los answers-- oh no, las necesidades de la línea que va a copiar, oh querida mí).

En cuanto a multihilo, como dije esto está diseñado para ser utilizado en serie. No, no es hilo de seguridad, pero no me importa.

Gracias por la multitud de respuestas, sin embargo! Voy a leer más a fondo cuando llegue el tiempo. Actualmente, me estoy inclinando hacia ya sea pasando un puntero extra alrededor o rediseñar la función de modo que cuando espectáculos fgets EOF, entonces yo podría construir la lógica liberando allí en vez de esperar y el usuario no tendrá que preocuparse por ello.

¿Fue útil?

Solución

Si se puede cambiar la función, recomendaría cambiar la función propia interfaz. Sé que ha pasado mucho tiempo de depuración y prueba de ello, pero hay algunos problemas con su implementación actual:

  • no es hilo de seguridad,
  • el usuario no tiene control sobre los datos, por lo que debe copiarlo si lo necesita más adelante, probablemente en un búfer que va a ser malloc()ed, anulando así cualquier ventaja que tienes por el uso selectivo de malloc() en su función,
  • lo más importante, como lo han descubierto, una acción especial tiene que ser tomada por el usuario durante mucho última línea.

Sus usuarios no debería estar preocupado por la rareza implementación de su función, deben ser capaces de "sólo lo utilizan".

A menos que usted lo está haciendo con fines educativos, recomendaría mirando este , que tiene una aplicación de "leer una línea de longitud arbitraria de una corriente", y enlaces a otras implementaciones (cada aplicación es ligeramente diferente de los demás, por lo que debe ser capaz de encontrar uno que te gusta) .

Sobre la base de su edición, MT-seguro no es un requisito, y una copia siempre va a suceder. Por lo tanto, el diseño más obvia es uno de los dos:

  • Deje que el usuario suministra una char **, que apunta a un búfer que su función asignará, utilizando una combinación de malloc() y realloc() (si es necesario). Es responsabilidad del usuario para free() que cuando haya terminado. De esta manera, el usuario no tiene que copiar los datos de nuevo, ya que puede pasar un puntero a dondequiera que el destino final de los datos es.
  • devolver un char * que se le asigna en su función. De nuevo, es responsabilidad del usuario para free() él.

Ambos son más o menos equivalentes.

Para su implementación actual, siempre se puede volver "no termina de archivo" si la última línea es muy larga, y no terminar en una nueva línea. A continuación, el usuario va a llamar a su función de nuevo, y entonces usted puede liberar su memoria intermedia. En lo personal, yo sería más feliz con una función que me permite leer tantas líneas como yo quiero, y no me obligan a ir al final del archivo.

Otros consejos

Aparte de la dificultad de liberar ese búfer asignado dinámicamente, hay otro problema potencial. No es seguro para subprocesos. Dado que es una función de la biblioteca, a continuación, siempre existe la posibilidad de que se va a utilizar en un entorno multi-hilo en el futuro.

Probablemente sería mejor exigir la función de llamar a liberar el búfer a través de una función de biblioteca relacionada.

Eso todavía podría estar bien si se utiliza la técnica estándar para indicar fin de archivo (es decir, tienen que leer línea función nula de retorno).

Lo que sucede en este caso es que después se lee la última línea, será necesaria una más llamada a la función de su línea de lectura para que pueda devolver NULL para indicar que el final del archivo se ha alcanzado. En esta última llamada, puede entonces liberarse del búffer.

Dos opciones que se producen inmediatamente:

  1. Hacer el puntero al búfer de pila estática asignada, pero con ámbito archivo. Añadir una función (estática) que comprueba si no es nulo y si no es nulo libre () es todo. atexit llamada (free_func) al inicio del programa, donde free_func es la función estática. Puede tener alguna rutina de instalación mundial (caled por main ())

  2. donde se hace esto.
  3. No se preocupe por ella; la pila de memoria asignada es liberada por el sistema operativo cuando sus salidas de proceso, y la pérdida de memoria no es acumulativo, por lo que incluso si el programa tiene una larga vida no va a lanzar una excepción OOM (a menos que tenga algún otro insecto).

Asumo que su aplicación no es multiproceso; en este caso, no se debe utilizar un buffer estático en absoluto, o que debe utilizar los datos de subproceso local.

La interfaz que haya elegido hace de este un problema sin solución:

  • El cliente no debe saber si el valor devuelto apunta a estática o dinámica de memoria.

  • El valor de retorno debe apuntar a la memoria que sobrevive a la llamada.

  • Cualquier llamada podría ser el último.

No estoy seguro de por qué usted está preocupado por esta filtración. Después de todo, si el cliente lee una línea muy larga, hace algo con la línea, luego hace una tonelada de cálculo y asignación antes de leer la siguiente línea, usted todavía tiene un gran pedazo de la memoria sentados alrededor sin usar, obstruyendo el sistema. Si esta bien con usted (computación arbitraria se lleva a cabo antes de que se reclame la memoria), sólo podía confesar que está dispuesto para retener la memoria muerta por tiempo indefinido.

Si usted no puede vivir con la fuga, lo más sencillo que hacer es ampliar la interfaz para que el cliente puede notificar a su función cuando el cliente se realiza con la memoria. (En este momento el contrato con el cliente dice que el cliente posee la memoria hasta que llama a su función de nuevo, por lo que revierte la propiedad punto a su función.) Por supuesto, para cambiar los medios de interfaz o bien

  • Añadir una nueva función, lo que requeriría que la promoción de su puntero para ser static pero local a la unidad de compilación, o

  • añadiendo un poco de argumento a la función ya existente (o sobrecargar un argumento) para que tenga una llamada que significa "he terminado con su memoria ahora, pero no quiere otra línea".

Un cambio más radical sería volver a escribir la función de utilizar la memoria asignada dinámicamente a lo largo de su vida útil, aumentando gradualmente el bloque como sea necesario hasta que sea tan grande como el bloque más grande jamás leyó (o tal vez redondea a la siguiente potencia de dos ). Dependiendo de los casos reales de esta estrategia puede consumir menor espacio de direcciones que mantener un buffer estático grande.

En cualquier caso, no estoy convencido de que debe ser preocupante sobre este caso esquina. Si cree que este caso los asuntos, por favor editar su pregunta que nos muestre las pruebas.

En lugar de ámbito de la función, darle ámbito de módulo (es decir, en el ámbito de archivo, pero estática, por lo que no es visible fuera de ese archivo. Añadir una pequeña función que libera la memoria intermedia, y el uso atexit() para asegurar que se llama antes de salir del programa. alternativa, no se preocupe por ella - una fuga que ocurre sólo una vez, y se libera automáticamente a medida que el programa se cierra no es particularmente dañina

.

Me siento obligado a decir que los sonidos de diseño a mí como una receta para el desastre sin embargo. Tras liberar la memoria intermedia, prácticamente no hay manera de adivinar, incluso si todavía podría estar en uso. El usuario (aparentemente) tiene que mantener un registro de las que se devuelva los datos y copiar los datos a un nuevo buffer si (y sólo si) asignado uno dinámicamente. En un entorno de múltiples hilos, lo que necesita para hacer que el puntero interno local de subprocesos para tener alguna posibilidad de funcionar correctamente en absoluto. Para el usuario, la función podría hacer una de dos cosas completamente diferentes - ya sea devolver un búfer que es propiedad por el usuario, o devolver un búfer que es propiedad de la función, y sólo puede ser utilizado con seguridad mediante la asignación de otro buffer, y la copia de la datos en la otra memoria intermedia antes de la función se llama de nuevo.

Hay algunos trucos que se me ocurren, aunque ambos requieren mover la declaración estática de la función. No me puedo imaginar por qué eso sería un problema.

El uso de un extensión GCC ,

static char *buffer;
void use_buffer(size_t n) {
    buffer = realloc(buffer, n);
}
void cleanup_buffer() __attribute__((destructor)) {
    free(buffer);
}

Uso de C ++,

static char *buffer;
static class buffer_guard {
    ~buffer_guard() { free(buffer); }
} my_buffer_guard;

En cualquier caso, no me gusta mucho el diseño. En C, por lo general la persona que llama es responsable de la asignación / liberación de memoria que se necesita para usar, incluso si es rellenado por un destinatario de la llamada.

Por cierto, comparar con Glibc no estándar de getline . Nunca utiliza memoria estática.

Yo estaba a punto de comentarios a continuación la respuesta de la marca, pero puede sentir un poco estrecha. Sin embargo, esta respuesta es, en esencia, un comentario en su respuesta, que me parece muy buena, además de ser rápido:.)

No sólo es su función no MT-seguro, pero incluso sin hilos, la interfaz para usarlo correctamente es complicado. La persona que llama debe haber terminado con el resultado anterior antes de llamar a la función de nuevo. Si el código está todavía en uso dentro de dos años, alguien va a rascarse la cabeza tratando de utilizar las cosas bien ... o peor, lo utilizan mal sin pensar siquiera en ello. Esa persona podría incluso ser usted ...

sugerencia de Mark (que requiere la persona que llama para liberar el buffer) es el más razonable en mi humilde opinión. Pero tal vez no lo hace malloc confianza y free no causa la fragmentación en el largo plazo, o tienen alguna otra razón para preferir la solución buffer estático. En este caso se puede mantener el buffer estático para las líneas de longitud ordinaria, definir un indicador booleano que indica si el buffer estático esté ocupado, y el documento que la siguiente función (y no free) debe ser llamado con la dirección del búfer cuando su interlocutor no lo utiliza:

char static_buffer[512];
int buffer_busy;

void free_buffer(char *p)
{
  if (p == static_buffer)
  {
     assert(buffer_busy);
     buffer_busy=0;
  }
  else free(p);
}

char *get_line(...)
{
  char *result;
  if (..short line..)
  {
     result = static_buffer;
     assert(!buffer_busy);
     buffer_busy=1;
  }
  else result = malloc(...);
  ...
  return result;
}

Las únicas circunstancias en las que las afirmaciones serán desencadenar circunstancias en las que su aplicación anterior tendría mal en silencio se ha ido, y la sobrecarga es muy baja en comparación con la solución existente (sólo alternar la bandera, y pidiendo a la persona que llama para llamar free_buffer cuando Ha terminado, lo que es más limpio). Si la afirmación en get_line en disparadores particulares, significa que necesita la asignación dinámica después de todo, ya que la persona que llama no se podía acabar con una memoria intermedia en el momento que estaba pidiendo otra.

Nota:. Esto todavía no es segura MT-

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top