El uso de malloc()
y free()
parece bastante raro en el mundo Arduino. Se usa en AVR C puro con mucha más frecuencia, pero con precaución.
¿Es realmente una mala idea usar malloc()
y free()
con Arduino?
programming
sram
eeprom
Cybergibbons
fuente
fuente
Respuestas:
Mi regla general para sistemas embebidos es solo
malloc()
grandes buffers y solo una vez, al inicio del programa, por ejemplo, ensetup()
. El problema surge cuando asigna y desasigna memoria. Durante una sesión de ejecución larga, la memoria se fragmenta y, finalmente, una asignación falla debido a la falta de un área libre suficientemente grande, a pesar de que la memoria libre total es más que adecuada para la solicitud.(Perspectiva histórica, omita si no le interesa): según la implementación del cargador, la única ventaja de la asignación en tiempo de ejecución frente a la asignación en tiempo de compilación (globales inicializados) es el tamaño del archivo hexadecimal. Cuando los sistemas embebidos se construían con computadoras listas para usar que tenían toda la memoria volátil, el programa a menudo se cargaba al sistema embebido desde una red o una computadora de instrumentación y el tiempo de carga a veces era un problema. Dejar buffers llenos de ceros de la imagen podría acortar el tiempo considerablemente).
Si necesito asignación de memoria dinámica en un sistema embebido, generalmente
malloc()
, o preferiblemente, asigno estáticamente, un grupo grande y lo divido en búferes de tamaño fijo (o un grupo de búferes pequeños y grandes, respectivamente) y hago mi propia asignación / desasignación de ese grupo. Entonces, cada solicitud de cualquier cantidad de memoria hasta el tamaño del búfer fijo se atiende con uno de esos búferes. La función de llamada no necesita saber si es más grande de lo solicitado, y al evitar dividir y volver a combinar bloques, resolvemos la fragmentación. Por supuesto, aún pueden producirse pérdidas de memoria si el programa tiene errores de asignación / desasignación.fuente
Normalmente, al escribir bocetos de Arduino, evitará la asignación dinámica (ya sea con
malloc
onew
para instancias de C ++), las personas prefieren utilizarstatic
variables globales o variables locales (pila).El uso de la asignación dinámica puede generar varios problemas:
malloc
/free
llamadas), donde la pila se hace más grande thant la cantidad real de memoria asignada actualmenteEn la mayoría de las situaciones que he enfrentado, la asignación dinámica no era necesaria o podría evitarse con macros como en el siguiente ejemplo de código:
MySketch.ino
Dummy.h
Sin esto
#define BUFFER_SIZE
, si quisiéramos que laDummy
clase tuviera unbuffer
tamaño no fijo , tendríamos que usar la asignación dinámica de la siguiente manera:En este caso, tenemos más opciones que en la primera muestra (p. Ej., Usar diferentes
Dummy
objetos con diferentesbuffer
tamaños para cada uno), pero podemos tener problemas de fragmentación del montón.Tenga en cuenta el uso de un destructor para garantizar que la memoria asignada dinámicamente
buffer
se liberará cuandoDummy
se elimine una instancia.fuente
He echado un vistazo al algoritmo utilizado por
malloc()
avr-libc, y parece que hay algunos patrones de uso que son seguros desde el punto de vista de la fragmentación del montón:1. Asigne solo buffers de larga duración
Con esto quiero decir: asigne todo lo que necesita al comienzo del programa, y nunca lo libere. Por supuesto, en este caso, también podría usar buffers estáticos ...
2. Asigne solo buffers de corta duración
Significado: libera el búfer antes de asignar cualquier otra cosa. Un ejemplo razonable podría verse así:
Si no hay malloc en el interior
do_whatever_with()
, o si esa función libera lo que asigne, entonces está a salvo de la fragmentación.3. Libere siempre el último búfer asignado
Esta es una generalización de los dos casos anteriores. Si usa el montón como una pila (el último en entrar es el primero en salir), entonces se comportará como una pila y no se fragmentará. Cabe señalar que en este caso es seguro cambiar el tamaño del último búfer asignado con
realloc()
.4. Siempre asigne el mismo tamaño
Esto no evitará la fragmentación, pero es seguro en el sentido de que el montón no crecerá más que el tamaño máximo utilizado . Si todas sus memorias intermedias tienen el mismo tamaño, puede estar seguro de que, cuando libere una de ellas, la ranura estará disponible para asignaciones posteriores.
fuente
El uso de la asignación dinámica (a través de
malloc
/free
onew
/delete
) no es inherentemente malo como tal. De hecho, para algo como el procesamiento de cadenas (por ejemplo, a través delString
objeto), a menudo es bastante útil. Esto se debe a que muchos bocetos usan varios fragmentos pequeños de cadenas, que eventualmente se combinan en uno más grande. El uso de la asignación dinámica le permite usar solo la cantidad de memoria que necesita para cada uno. En contraste, usar un buffer estático de tamaño fijo para cada uno podría terminar desperdiciando mucho espacio (haciendo que se quede sin memoria mucho más rápido), aunque depende completamente del contexto.Dicho todo esto, es muy importante asegurarse de que el uso de la memoria sea predecible. Permitir que el boceto use cantidades arbitrarias de memoria dependiendo de las circunstancias del tiempo de ejecución (por ejemplo, entrada) puede causar un problema fácilmente tarde o temprano. En algunos casos, puede ser perfectamente seguro, por ejemplo, si sabe que el uso nunca sumará demasiado. Sin embargo, los bocetos pueden cambiar durante el proceso de programación. Una suposición hecha desde el principio podría olvidarse cuando algo se cambia más tarde, lo que resulta en un problema imprevisto.
Para mayor robustez, generalmente es mejor trabajar con memorias intermedias de tamaño fijo siempre que sea posible, y diseñar el boceto para que funcione explícitamente con esos límites desde el principio. Eso significa que cualquier cambio futuro en el boceto, o cualquier circunstancia inesperada en el tiempo de ejecución, no debería causar problemas de memoria.
fuente
No estoy de acuerdo con las personas que piensan que no deberías usarlo o que generalmente es innecesario. Creo que puede ser peligroso si no conoce los entresijos, pero es útil. Tengo casos en los que no sé (y no debería importarme saber) el tamaño de una estructura o un búfer (en tiempo de compilación o tiempo de ejecución), especialmente cuando se trata de bibliotecas que envío al mundo. Estoy de acuerdo en que si su aplicación solo trata con una estructura única y conocida, debe hornear ese tamaño en el momento de la compilación.
Ejemplo: tengo una clase de paquete en serie (una biblioteca) que puede tomar cargas de datos de longitud arbitraria (puede ser struct, array de uint16_t, etc.). Al final del envío de esa clase, simplemente le dice al método Packet.send () la dirección de la cosa que desea enviar y el puerto HardwareSerial a través del cual desea enviarlo. Sin embargo, en el extremo receptor, necesito un búfer de recepción asignado dinámicamente para mantener esa carga útil entrante, ya que esa carga podría ser una estructura diferente en cualquier momento dado, dependiendo del estado de la aplicación, por ejemplo. SI solo envío una sola estructura de un lado a otro, simplemente haría que el búfer tenga el tamaño que necesita en el momento de la compilación. Pero, en el caso de que los paquetes puedan tener diferentes longitudes en el tiempo, malloc () y free () no son tan malos.
He realizado pruebas con el siguiente código durante días, lo que permite que se repita continuamente, y no he encontrado evidencia de fragmentación de la memoria. Después de liberar la memoria asignada dinámicamente, la cantidad libre vuelve a su valor anterior.
No he visto ningún tipo de degradación en la RAM o en mi capacidad de asignarla dinámicamente usando este método, por lo que diría que es una herramienta viable. FWIW
fuente
La respuesta corta es sí. A continuación se detallan los motivos:
Se trata de comprender qué es una MPU y cómo programar dentro de las limitaciones de los recursos disponibles. El Arduino Uno utiliza una MPU ATmega328p con memoria flash ISP de 32KB, EEPROM 1024B y SRAM de 2KB. Eso no es una gran cantidad de recursos de memoria.
Recuerde que la SRAM de 2 KB se usa para todas las variables globales, literales de cadena, pila y posible uso del montón. La pila también necesita tener espacio para un ISR.
El diseño de la memoria es:
Las PC / laptops actuales tienen más de 1,000,000 de veces la cantidad de memoria. Un espacio de pila predeterminado de 1 Mbyte por subproceso no es infrecuente pero totalmente irreal en una MPU.
Un proyecto de software integrado tiene que hacer un presupuesto de recursos. Esto es estimar la latencia ISR, el espacio de memoria necesario, la potencia de cómputo, los ciclos de instrucción, etc. Desafortunadamente no hay almuerzos gratis y la programación incrustada en tiempo real es la más difícil de las habilidades de programación para dominar.
fuente
Ok, sé que esta es una vieja pregunta, pero cuanto más leo las respuestas, más vuelvo a una observación que parece sobresaliente.
El problema de detención es real
Parece que hay un vínculo con el problema de detención de Turing aquí. Permitir la asignación dinámica aumenta las probabilidades de dicha 'detención', por lo que la pregunta se convierte en una de tolerancia al riesgo. Si bien es conveniente descartar la posibilidad de
malloc()
fallar, etc., sigue siendo un resultado válido. La pregunta que hace el OP solo parece ser sobre la técnica, y sí, los detalles de las bibliotecas utilizadas o la MPU específica son importantes; la conversación gira hacia la reducción del riesgo de que el programa se detenga o cualquier otro final anormal. Necesitamos reconocer la existencia de entornos que toleran el riesgo de manera muy diferente. Mi proyecto de pasatiempo para mostrar colores bonitos en una tira de LED no matará a alguien si sucede algo inusual, pero la MCU dentro de una máquina de corazón y pulmón probablemente lo hará.Hola señor Turing, mi nombre es Hubris
Para mi tira de LED, no me importa si se bloquea, lo restableceré. Si estuviese en una máquina corazón-pulmón controlada por un MCU, las consecuencias de que se bloqueara o no funcionara son literalmente la vida y la muerte, por lo que la pregunta sobre
malloc()
y cómofree()
debería dividirse entre cómo el programa previsto trata con la posibilidad de demostrarle al Sr. El famoso problema de Turing. Puede ser fácil olvidar que es una prueba matemática y convencernos de que si solo somos lo suficientemente inteligentes podemos evitar ser víctimas de los límites de la computación.Esta pregunta debe tener dos respuestas aceptadas, una para aquellos que se ven obligados a parpadear al mirar El problema detenido en la cara, y otra para todos los demás. Si bien la mayoría de los usos del arduino probablemente no sean aplicaciones de misión crítica o de vida o muerte, la distinción sigue existiendo independientemente de qué MPU esté codificando.
fuente
No, pero deben usarse con mucho cuidado en lo que respecta a liberar () la memoria asignada. Nunca he entendido por qué la gente dice que se debe evitar la gestión directa de la memoria, ya que implica un nivel de incompetencia que generalmente es incompatible con el desarrollo de software.
Digamos que estás usando tu arduino para controlar un dron. Cualquier error en cualquier parte de su código podría causar que se caiga del cielo y lastime a alguien o algo. En otras palabras, si alguien carece de las competencias para usar malloc, es probable que no deba codificar en absoluto, ya que hay muchas otras áreas donde los pequeños errores pueden causar problemas graves.
¿Los errores causados por malloc son más difíciles de rastrear y corregir? Sí, pero eso es más una cuestión de frustración por parte de los codificadores que de riesgo. En lo que respecta al riesgo, cualquier parte de su código puede ser igual o más arriesgado que malloc si no sigue los pasos para asegurarse de que se haga correctamente.
fuente