32- Trabajando con memoria dinámica Arduino


Asignación de memoria en Arduino.

En esta lección, aprenderemos a trabajar con la memoria dinámica de Arduino. En primer lugar, debe familiarizarse con la asignación de memoria y comprender cómo funciona y qué haremos generalmente. Aquí hay un diagrama de la asignación de memoria en el microcontrolador AVR, que está en el Arduino:

Asignación de memoria en arduino
Asignación de memoria en arduino

La memoria es esencialmente una gran matriz, cada celda de la cual tiene su propia dirección, la dirección crece de izquierda a derecha (como se muestra en la imagen de arriba). La primera es Flash, también es la memoria de programa, la memoria en la que se almacena el código en sí. Este código no cambia mientras el programa se está ejecutando (hay formas de hacerlo, pero este no es el tema de la lección). Hoy nos interesa la memoria dinámica, SRAM en arduino, que en el diagrama está representada por una combinación de áreas azules, verdes, rosas y naranjas, así como una zona blanca con flechas entre rosa y naranja. Miremos más de cerca:

  • Globales (azul y verde): esta área es donde viven las variables globales y estáticas. El tamaño de esta área se conoce en el momento en que se inicia el programa y no cambia durante su ejecución, ya que se declaran variables globales y estáticas, se conoce su tamaño y número.
  • Heap (rosa), desde esta área podemos asignar memoria para nuestras necesidades. El tamaño de esta área puede cambiar durante la ejecución del programa, el montón «crece» en la dirección creciente, de izquierda a derecha, lo que se muestra con la flecha en el diagrama. En esta área, podemos asignar memoria de forma independiente para nuestras necesidades y liberarla, nuevamente por nuestra cuenta. Importante: el procesador no le permitirá asignar un área si no hay suficiente memoria libre para ello, es decir, es muy poco probable que la zona Heap y la Pila se solapen.
  • Stack, también conocido como Pila: en esta área viven variables y parámetros locales que se pasan a funciones (variables formales). El tamaño de esta área cambia durante la ejecución del programa, la pila crece desde el final del área de memoria hacia direcciones decrecientes, hacia el Heap (ver la flecha en el diagrama). Las variables que viven aquí se llaman automáticas: el programa en sí asigna la memoria (al crear una variable local al ingresar a la función), y libera esta memoria después (la variable local se borra cuando sale de la función). Importante: el procesador no controla el tamaño de la pila, es decir, durante la ejecución, la pila puede chocar con el heap y sobrescribir los datos que se encuentran allí. Si es que pones datos allí.
  • Disponible: memoria libre disponible. Tan pronto como esta se termine, el heap y la pila chocan: lo más probable es que el programa se cuelgue. Si la pila controla su tamaño de forma independiente, entonces debe tener cuidado con la memoria dinámica: no olvide liberarla si ya no la necesita.

Asignación de memoria.

Tenemos dos funciones para asignar y liberar la memoria del Heap: malloc() y free() respectivamente. También hay operadores new y delete que hacen lo mismo. Al asignar memoria, obtenemos la dirección del primer byte del área asignada, por lo que recomiendo leer la lección sobre direcciones y punteros .

  • malloc ( cantidad )– asigna el número de bytes de memoria dinámica (del heap) y devuelve la dirección al primer byte del área asignada. Si no hay suficiente memoria libre para la asignación, devuelve un «puntero nulo» –NULL.
  • free( ptr ) – libera la memoria apuntada por ptr, de vuelta al heap. Obtenemos la dirección como resultado de malloc () durante la asignación. Solo se puede liberar la memoria asignada mediante funciones malloc (), realloc () o calloc (). El área asignada almacena el tamaño de esta área (+2 bytes), y cuando se libera, la función free () sabe qué tamaño liberar.
  • new y delete – técnicamente igual, diferencia en la aplicación (ver ejemplo a continuación)

Un punto importante: necesita liberar memoria en el orden inverso de su asignación para evitar la fragmentación de la memoria, es decir, cuando hay espacios vacíos entre áreas ocupadas.

Veamos un ejemplo de asignación y liberación de memoria usando malloc / free y new / delete. Los ejemplos son absolutamente iguales desde el punto de vista de lo que está sucediendo, difieren solo en funciones:

mallloc / free

void setup() {
 // asigna memoria para variables de 10 bytes (10 bytes)
  byte *by = malloc(10);
  // asigna memoria para 20 variables de tipo int (40 bytes)
  int *in = malloc(20 * sizeof(int));
  // asigna memoria para 1 variable de tipo uint32_t (4 bytes)
  uint32_t *ui = malloc(4);
  //  trabaja con la matriz
  by[0] = 50;
  by[1] = 60;
  by[2] = 90;
  uart.println(by[0]);
  uart.println(by[1]);
  uart.println(by[2]);
  // con una variable regular
  *ui = 123456;
  free(ui); // liberamos
  free(in); // liberamos
  free(by); // liberamos
  // aquí * ¡ui es cero! Lo "borramos"
}
void loop() {}

new / delete

void setup() {
  // asigna memoria para variables de 10 bytes (10 bytes)
  byte *by = new byte [10];
  // asigna memoria para 20 variables de tipo int (40 bytes)
  int *in = new int [20];
  // asigna memoria para 1 variable de tipo uint32_t (4 bytes)
  uint32_t *ui = new uint32_t;
  // trabajar con la matriz
  by[0] = 50;
  by[1] = 60;
  by[2] = 90;
  // con una variable regular
  *ui = 123456;
  delete ui;    // liberamos
  delete [] in; //liberamos (indica que se trata de una matriz)
  delete [] by; // liberamos (indica que se trata de una matriz)
  // ¡aquí * ui y otros son iguales a cero! Los "borramos"
}
void loop() {}

Por lo tanto, hemos asignado memoria, podemos interactuar con esta memoria (como con una variable ordinaria de arduino) y luego liberarla. Permítanme recordarles que es muy deseable liberar en orden inverso, para que la memoria se libere secuencialmente sin dejar huecos.

Hay dos funciones más: calloc () y realloc ():

  • calloc ( cantidad, tamaño )– asigna memoria para el número de elementos con el tamaño de cada uno (en bytes). El mismo que malloc(), pero es un poco más conveniente de usar: en el ejemplo anterior, multiplicamos para obtener la cantidad requerida de bytes para el almacenamiento int malloc ( 20 * sizeof( int )) , pero podrías llamar calloc ( 20, sizeof( int )) ; – reemplazando el signo de multiplicación con una coma.
  • realloc ( ptr, tamaño )– cambia la cantidad de memoria asignada apuntada por ptr por un nuevo valor especificado por el parámetro de tamaño. El tamaño se especifica en bytes y puede ser mayor o menor que el original. Se devuelve un puntero a un bloque de memoria porque puede ser necesario mover el bloque a medida que aumenta su tamaño. En este caso, el contenido del bloque antiguo se copia en el bloque nuevo y no se pierde información.

Gestión de memoria por lotes.

Entonces, aprendimos cómo asignar y liberar memoria, ahora consideraremos varias herramientas para trabajar adecuadamente con memoria dinámica arduino.

  • memset ( ptr, value, num)– llena el número de piezas (num) de memoria apuntada por ptr con bytes de valor (value). A menudo se utiliza para establecer los valores iniciales de un área de memoria asignada. ¡Atención! solo con bytes, 0-255.
  • memcpy ( ptr1, ptr2, num)– sobrescribe los bytes del área ptr2 a ptr1 en la cantidad de (num). En términos generales, reescribe una matriz en otra. ¡Atención! ¡Funciona con bytes!
// === memset ===
// asigna 50 bytes
byte * buf = malloc ( 50 ) ;
// los llena con un valor de 10
memset ( buf, 10, 50 ) ;
// === memcpy ===
// una matriz
byte data1 [] = { 1, 2, 3, 4, 5 } ;
// y otra matriz
// byte data2 [5]; // se puede asignar desde la "pila"
byte * datos2 = malloc ( 5 ) ; // puede ser del "heap"
// reescribir data1 en data2
memcpy ( datos2, datos1, 5 ) ;
// data2 ahora es 1 2 3 4 5

Hemos aprendido a gestionar la memoria dinámica del microcontrolador de arduino. ¡Felicidades! Pero, ¿qué tan útil es esta herramienta? El tema es controvertido. Me encontré trabajando con memoria dinámica solo en bibliotecas de pantallas y tiras de LED direccionables, es decir, allí se creó un búfer en el heap, en el que se almacenaron los bytes de datos. También es posible hacer solo una matriz en la pila o en el alcance de las variables globales.

También puede ocurrir: el IDE de Arduino puede estimar aproximadamente el tamaño de la memoria SRAM ocupada en la etapa de compilación, las variables globales, pero las matrices creadas durante la ejecución es difícil predecir «como se almacenarán allí ”. Debe trabajar con cuidado con la memoria dinámica, recuerde limpiarla y recordar la fragmentación. La memoria dinámica es conveniente cuando necesita poner cierta cantidad de datos en un búfer que tiene un alcance grande, a diferencia de una variable local. Puede interactuar con este búfer desde diferentes partes del programa (pasando su dirección) y luego liberar memoria. Por lo tanto, es muy probable que trabajar con memoria dinámica no le resulte útil, pero es bueno recordar esta herramienta.


Deja un comentario