SDK Multiplataforma en C logo

SDK Multiplataforma en C

Heap - Gestor de memoria

❮ Anterior
Siguiente ❯

Gestor de memoria dinámica optimizado y control de leaks.


Funciones

voidheap_start_mt (void)
voidheap_end_mt (void)
voidheap_verbose (...)
voidheap_stats (...)
bool_theap_leaks (void)
byte_t*heap_malloc (...)
byte_t*heap_calloc (...)
byte_t*heap_realloc (...)
byte_t*heap_aligned_malloc (...)
byte_t*heap_aligned_calloc (...)
byte_t*heap_aligned_realloc (...)
voidheap_free (...)
type*heap_new (...)
type*heap_new0 (...)
type*heap_new_n (...)
type*heap_new_n0 (...)
type*heap_realloc_n (...)
voidheap_delete (...)
voidheap_delete_n (...)
voidheap_auditor_add (...)
voidheap_auditor_delete (...)

Heap es gestor y auditor de memoria dinámica muy eficiente incluido en la librería core y disponible para todos los proyectos basados en NAppGUI (librerías y aplicaciones). Es habitual que las aplicaciones soliciten gran cantidad de pequeños bloques de memoria para albergar diferentes objetos (cadenas de caracteres, controles de interfaz, instancias de estructuras, búfer E/S, etc). La estrategia detrás de gestor no es otra que pedir al sistema operativo páginas de memoria de cierto tamaño (64kb o más) mediante bmem_malloc y utilizarlas para resolver varias solicitudes de manera muy eficiente.

El uso de Heap en lugar de llamadas al sistema nos proporcionará ciertos beneficios:

  • Rendimiento: Una llamada a heap_malloc se resuelve tan solo incrementando el valor de un contador. heap_free únicamente actualiza la cabecera de la página afectada.
  • Localidad: Dos llamadas consecutivas a heap_malloc() son ubicadas en posiciones físicas de memoria contiguas. Esto reduce el número de fallos de caché ya que, según el principio de localidad, hay una gran probabilidad de que dos objetos que se crean juntos se utilicen juntos.
  • Memory leaks: heap apunta las reservas y liberaciones por tipo de objeto. Llegado el caso, avisará al programador por medio de Asserts o Log de que existen objetos no liberados. La gran ventaja de este auditor sobre otras herramientas es que siempre se está ejecutando como parte del programa. Esto explota la coherencia temporal, ya que si tras un cambio se detectan leaks donde antes no había, es muy probable que podamos acotar y detectar el error, ya que será algo en lo que acabamos de trabajar.
  • Estadísticas: Podemos obtener perfiles del uso de memoria (tiempo/bytes). Esto puede ayudarnos a detectar cuellos de botella (especialmente en el arranque) u optimizar el tamaño de página.

1. Memoria múlti-hilo

Por defecto, heap está configurado para funcionar de forma óptima en aplicaciones mono-hilo. Si queremos que varias hebras del mismo proceso reserven o liberen memoria dinámica de forma concurrente y segura deberemos utilizar:

En el momento que se llama a heap_start_mt, se activan los mecanismos de sincronización dentro del Heap para garantizar la exclusión mutua al gestor de memoria hasta que se reciba una llamada a heap_end_mt que lo devolverá al modo de funcionamiento mono-hilo. Llamadas sucesivas a heap_start_mt se irán acumulando, por lo que permanecerá en modo multi-hilo hasta que se cierren todos los bloques abiertos (Listado 1). Es responsabilidad del programador utilizar este par de funciones en aquellos puntos del programa que lo requieran.

Toda sección que comienza con heap_start_mt debe cerrarse con heap_end_mt.
No hay ningún problema en activar el soporte multi-hilo en secciones mono-hilo, salvo una ligera penalización en el rendimiento.
Listado 1: Secciones multi-hilo.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Single-threaded block
...
...

heap_start_mt();
// Multi-threaded block
...
heap_start_mt();
...
heap_end_mt();
// Continue multi-threaded block
...
heap_end_mt();

// Single-threaded block
...

2. Como funciona Heap

Al iniciar el programa, heap crea una página de memoria por defecto. Los primeros bytes se reservan como cabecera, una pequeña estructura que controla el estado de la página. Cada petición se asigna secuencialmente dentro de la misma página, incrementando el valor de un puntero (Figura 1). Cuando la página se queda sin espacio, se crea una nueva mediante bmem_malloc, que es enlazada con la anterior y etiquetada como la nueva página por defecto (Figura 2). Cada llamada a heap_free actualiza la cabecera con el número de bloques/bytes liberados (Figura 3). Estos bloques no son reutilizados, de lo contrario se complicaría la lógica de heap volviéndolo más lento. La dirección de la cabecera es guardada al final de cada bloque, por lo que no hay que iterar para localizarlo. Cuando todos los bloques de la página han sido liberados, la página entera es destruida por bmem_free y los punteros entre páginas vecinas restaurados (Figura 4).

Esquema de una página de memoria al ejecutar malloc.
Figura 1: Reserva de un nuevo bloque de memoria con heap_malloc().
Petición al sistema operativo de una nueva página de memoria.
Figura 2: Petición al sistema operativo de una nueva página vacía.
Esquema de una página de memoria al ejecutar free.
Figura 3: Liberando un bloque de memoria (solo actualiza la cabecera).
Destrucción de una página de memoria cuando todos sus elementos han sido liberados.
Figura 4: Destruyendo la página entera.

Heap también contabiliza el número de reversas/liberaciones por tipo de objeto mediante el parámetro name de heap_malloc. Al finalizar la ejecución del programa, si la aplicación carece de memory leaks, escribirá en Log un mensaje como este:

1
2
3
4
5
6
7
8
[12:58:08] [OK] Heap Memory Staticstics
[12:58:08] ============================
[12:58:08] Total a/dellocations: 1126, 1126
[12:58:08] Total bytes a/dellocated: 74611, 74611
[12:58:08] Max bytes allocated: 54939
[12:58:08] Effective reallocations: (0/34)
[12:58:08] Real allocations: 2 pages of 65536 bytes
[12:58:08] ============================

Pero si tras la ejecución, la aplicación tiene memoria por liberar, el mensaje será distinto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[13:00:35] [FAIL] Heap Object Leaks!!!
[13:00:35] ===========================
[13:00:35] 'App' a/deallocations: 1, 0 (1 leaks)
[13:00:35] 'String' a/deallocations: 414, 410 (4 leaks)
[13:00:35] ===========================
[13:00:35] [FAIL] Heap Global Memory Leaks!!!
[13:00:35] ==================================
[13:00:35] Total a/dellocations: 1161, 1156 (5 leaks)
[13:00:35] Total bytes a/dellocated: 75704, 75596 (108 bytes)
[13:00:35] Max bytes allocated: 54939
[13:00:35] ==================================

Que advierte que tenemos un objeto App y cuatro String sin liberar. Si en la ejecución anterior no había leaks, es muy probable que podamos acotar el error sin demasiada dificultad.

El auditor de heap no pretende sustituir a herramientas más avanzadas de testeo de memoria, tan solo es un primer filtro que nos avisa constantemente durante la fase de desarrollo y test. A pesar de que la sobrecarga que produce en tiempo de ejecución es mínima, el auditor se deshabilita complemente en la configuración Release.

heap_start_mt ()

Inicia una sección multi-hilo.

void
heap_start_mt(void);

Observaciones

Ver Memoria múlti-hilo.


heap_end_mt ()

Finaliza una sección multi-hilo.

void
heap_end_mt(void);

Observaciones

Ver Memoria múlti-hilo.


heap_verbose ()

Activa/desactiva del modo 'verbose' del auditor de memoria.

void
heap_verbose(bool_t verbose);
verbose

TRUE para activar.

Observaciones

Por defecto FALSE.


heap_stats ()

Activa/desactiva las estadísticas del auditor de memoria.

void
heap_stats(bool_t stats);
stats

TRUE para activar.

Observaciones

Por defecto TRUE.


heap_leaks ()

Retorna TRUE si existen fugas de memoria al acabar la ejecución.

bool_t
heap_leaks(void);

Retorna

TRUE si existen fugas.


heap_malloc ()

Reserva un bloque de memoria con la alineación por defecto sizeof(void*).

byte_t*
heap_malloc(const uint32_t size,
            const char_t *name);
1
2
3
byte_t *mem = heap_malloc(1024 * 768, "PixelBuffer");
...
heap_free(&mem, 1024 * 768, "PixelBuffer");
size

Tamaño en bytes del bloque.

name

Texto de referencia para el auditor.

Retorna

Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.

Observaciones

Usa esta función para bloques genéricos. Para tipos utiliza heap_new.


heap_calloc ()

Igual que heap_malloc, pero inicializando el bloque con 0s.

byte_t*
heap_calloc(const uint32_t size,
            const char_t *name);
1
2
3
4
byte_t *mem = heap_calloc(256 * 256, "DrawCanvas");
/* mem = {0, 0, 0, 0, ..., 0}; */
...
heap_free(&mem, 256 * 256, "DrawCanvas");
size

Tamaño en bytes del bloque.

name

Texto de referencia para el auditor.

Retorna

Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.

Observaciones

Usa esta función para bloques genéricos. Para tipos utiliza heap_new.


heap_realloc ()

Realoja un bloque de memoria existente debido a la expansión o reducción del mismo. Garantiza que se conserva el contenido previo del bloque min(size, new_size). Intenta hacerlo sin mover memoria (in situ), pero si no es posible busca una nueva zona. También garantiza la alineación por defecto sizeof(void*) si hay que reservar un nuevo bloque.

byte_t*
heap_realloc(byte_t *mem,
             const uint32_t size,
             const uint32_t new_size,
             const char_t *name);
1
2
3
4
5
byte_t *mem = heap_malloc(64, "ArrayData");
...
mem = heap_realloc(mem, 64, 128, ArrayData);
...
heap_free(&mem, 128, "ArrayData");
mem

Puntero al bloque original a realojar.

size

Tamaño en bytes del bloque original mem.

new_size

Nuevo tamaño requerido, en bytes.

name

Texto de referencia para el auditor. Debe ser el mismo que el utilizado en heap_malloc.

Retorna

Puntero al bloque realojado. Será el mismo que el puntero original mem si la reubicación "in-situ" ha tenido éxito. Debe ser liberado con heap_free cuando ya no sea necesario.

Observaciones

Usa esta función para bloques genéricos. Para tipos utiliza heap_realloc_n.


heap_aligned_malloc ()

Reserva un bloque de memoria con alineación.

byte_t*
heap_aligned_malloc(const uint32_t size,
                    const uint32_t align,
                    const char_t *name);
1
2
3
byte_t *sse_data = heap_aligned_malloc(256 * 16, 16, "Vectors");
...
heap_free(&mem, 256 * 16, "Vectors");
size

Tamaño en bytes del bloque.

align

Alineación. Debe ser potencia de 2.

name

Texto de referencia para el auditor.

Retorna

Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.


heap_aligned_calloc ()

Igual que heap_aligned_malloc, pero inicializando el bloque con 0s.

byte_t*
heap_aligned_calloc(const uint32_t size,
                    const uint32_t align,
                    const char_t *name);
1
2
3
4
byte_t *sse_data = heap_aligned_calloc(256 * 16, 16, "Vectors");
/* see_data = {0, 0, 0, 0, ..., 0}; */
...
heap_free(&mem, 256 * 16, "Vectors");
size

Tamaño en bytes del bloque.

align

Alineación. Debe ser potencia de 2.

name

Texto de referencia para el auditor.

Retorna

Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.


heap_aligned_realloc ()

Igual que heap_realloc, pero garantizando la alineación de memoria.

byte_t*
heap_aligned_realloc(byte_t *mem,
                     const uint32_t size,
                     const uint32_t new_size,
                     const uint32_t align,
                     const char_t *name);
1
2
3
4
5
byte_t *sse_data = heap_aligned_malloc(256 * 16, 16, "Vectors");
...
sse_data = heap_aligned_realloc(sse_data, 256 * 16, 512 * 16, 16, "Vectors");
...
heap_free(&mem, 512 * 16, "Vectors");
mem

Puntero al bloque original a realojar.

size

Tamaño en bytes del bloque original mem.

new_size

Nuevo tamaño requerido, en bytes.

align

Alineación. Debe ser potencia de 2.

name

Texto de referencia para el auditor. Debe ser el mismo que el utilizado en heap_aligned_malloc.

Retorna

Puntero al bloque realojado. Debe ser liberado con heap_free cuando ya no sea necesario.


heap_free ()

Libera la memoria apuntada por mem, previamente reservada por heap_malloc, heap_realloc o sus equivalentes con alineación.

void
heap_free(byte_t **mem,
          const uint32_t size,
          const char_t *name);
mem

Doble puntero al bloque a liberar. Será puesto a NULL tras la liberación.

size

Tamaño del bloque de memoria.

name

Texto de referencia para el auditor, debe ser el mismo que el utilizado en heap_malloc.

Observaciones

Usa esta función para bloques de memoria genéricos. Para tipos utiliza heap_delete.


heap_new ()

Reserva memoria para un objeto. El puntero de retorno es convertido a type.

type*
heap_new(type);
1
2
3
MyAppCtrl *ctrl = heap_new(MyAppCtrl);
...
heap_delete(&ctrl, MyAppCtrl);
type

Tipo de objeto.

Retorna

Puntero al objeto creado. Debe ser destruido por heap_delete cuando ya no sea necesario.


heap_new0 ()

Igual que heap_new, pero inicializando el objeto con 0s.

type*
heap_new0(type);
1
2
3
4
MyAppModel *model = heap_new0(MyAppModel);
/* model = {0} */
...
heap_delete(&model, MyAppModel);
type

Tipo de objeto.

Retorna

Puntero al objeto creado. Debe ser destruido por heap_delete cuando ya no sea necesario.


heap_new_n ()

Reserva memoria para n objetos. El puntero de retorno es convertido a type.

type*
heap_new_n(const uint32_t n,
           type);
1
2
3
Car *cars = heap_new_n(10, Car);
...
heap_delete_n(&cars, 10, Car);
n

Número de objetos a crear.

type

Tipo de objeto.

Retorna

Puntero al array recién creado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.


heap_new_n0 ()

Igual que heap_new_n, pero inicializando el array con 0s.

type*
heap_new_n0(const uint32_t n,
            type);
1
2
3
4
Car *cars = heap_new_n0(10, Car);
/* cars = {0, 0, 0, ..., 0}; */
...
heap_delete_n(&cars, 10, Car);
n

Número de objetos a crear.

type

Tipo de objeto.

Retorna

Puntero al array recién creado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.


heap_realloc_n ()

Realoja un array de objetos creados dinámicamente con heap_new_n o heap_new_n0. Garantiza que los objetos previos permanezcan inalterados min(size, new_size).

type*
heap_realloc_n(type *mem,
               const uint32_t size,
               const uint32_t new_size,
               type);
1
2
3
4
5
6
Car *cars = heap_new_n(10, Car);
...
cars = heap_realloc_n(cars, 10, 20, Car);
/* cars[0]-[9] remains untouched. */
...
heap_delete_n(&cars, 20, Car);
mem

Puntero al array a realojar.

size

Número de elementos del array original mem.

new_size

Nuevo tamaño requerido (en elementos).

type

Tipo de objeto.

Retorna

Puntero al array realojado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.


heap_delete ()

Libera el objeto apuntado por obj, previamente reservado por heap_new o heap_new0.

void
heap_delete(type **obj,
            type);
obj

Doble puntero al objeto a liberar. Será puesto a NULL tras la liberación.

type

Tipo de objeto.


heap_delete_n ()

Libera n objetos apuntados por obj, previamente reservado por heap_new_n, heap_new_n0.

void
heap_delete_n(type **obj,
              const uint32_t n,
              type);
obj

Doble puntero al array a liberar. Será puesto a NULL tras la liberación.

n

Número de objetos a liberar, el mismo que en la reserva.

type

Tipo de objeto.


heap_auditor_add ()

Añade un objeto opaco al auditor de memoria.

void
heap_auditor_add(const char_t *name);
name

Nombre del objeto a añadir.


heap_auditor_delete ()

Libera un objeto opaco del auditor de memoria.

void
heap_auditor_delete(const char_t *name);
name

Nombre del objeto a liberar.

❮ Anterior
Siguiente ❯