采用malloc()
和free()
似乎在Arduino的世界非常罕见的。纯AVR C中使用它的频率更高,但仍要谨慎。
它是一个非常糟糕的主意,使用malloc()
和free()
与Arduino的?
采用malloc()
和free()
似乎在Arduino的世界非常罕见的。纯AVR C中使用它的频率更高,但仍要谨慎。
它是一个非常糟糕的主意,使用malloc()
和free()
与Arduino的?
Answers:
对于嵌入式系统,我的一般规则是malloc()
在程序开始时(例如,在中)仅对大型缓冲区进行一次缓冲setup()
。当您分配和取消分配内存时,问题就来了。在长期运行的会话中,内存会变得碎片化,最终由于缺少足够大的可用区域而导致分配失败,即使总的可用内存足以满足请求。
(从历史的角度来看,如果不感兴趣,请跳过):根据加载程序的实现,运行时分配与编译时分配(初始化的全局变量)的唯一优势是十六进制文件的大小。当使用具有所有易失性存储器的现成计算机构建嵌入式系统时,该程序通常是从网络或仪器计算机上载到嵌入式系统的,有时上载时间是个问题。从图像中遗漏零缓冲区可能会大大缩短时间。)
如果我需要在嵌入式系统中进行动态内存分配,则通常malloc()
(或最好是静态地)分配一个大池,然后将其划分为固定大小的缓冲区(或分别为小缓冲区和大缓冲区的一个池),然后自行分配/从该池中取消分配。然后,对那些不超过固定缓冲区大小的任何内存量的请求都将使用这些缓冲区之一进行响应。调用函数不需要知道它是否大于请求,并且通过避免拆分和重新组合块,我们可以解决碎片问题。当然,如果程序具有分配/取消分配错误,则仍然可能发生内存泄漏。
通常,在编写Arduino草图时,您将避免动态分配(对于C ++实例,malloc
或new
用于C ++实例),人们宁可使用全局-或static
-变量或局部(堆栈)变量。
使用动态分配会导致几个问题:
malloc
/ free
调用之后),其中堆增长大于当前分配的实际内存量在大多数情况下,我不需要动态分配,或者可以使用宏避免动态分配,如以下代码示例所示:
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
虚拟的
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
没有#define BUFFER_SIZE
,如果我们想让Dummy
类具有不固定的buffer
大小,则必须使用动态分配,如下所示:
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
在这种情况下,我们有比第一个示例更多的选项(例如,使用每个Dummy
对象具有不同buffer
大小的不同对象),但是我们可能会遇到堆碎片问题。
请注意,使用析构函数可确保buffer
在Dummy
删除实例时释放为其动态分配的内存。
我malloc()
从avr-libc 看了,使用的算法,从堆碎片的角度看,似乎有一些使用模式是安全的:
我的意思是:在程序开始时分配所有需要的内容,决不要释放它。当然,在这种情况下,您也可以使用静态缓冲区...
含义:在分配其他任何内容之前,先释放缓冲区。一个合理的示例可能如下所示:
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
如果内部没有malloc do_whatever_with()
,或者该函数释放了它分配的所有内容,那么您可以避免碎片。
这是前两种情况的概括。如果您像堆一样使用堆(后进先出),那么它将表现得像堆,而不是碎片。应该注意的是,在这种情况下,可以安全地调整最后分配的缓冲区的大小realloc()
。
这不会阻止碎片,但是从某种意义上讲,堆不会增长到大于最大已用大小是安全的。如果所有缓冲区的大小都相同,则可以确定,只要释放其中一个缓冲区,该插槽就可用于后续分配。
因此,使用动态分配(通过malloc
/ free
或new
/ delete
)并不是天生的坏事。实际上,对于诸如字符串处理之类的事情(例如通过String
对象),它通常非常有用。这是因为许多草图使用了几个小的字符串片段,最终将它们组合成一个更大的字符串。使用动态分配可使您仅使用每个内存所需的内存。相反,为每个缓冲区使用固定大小的静态缓冲区最终可能会浪费大量空间(导致其更快地耗尽内存),尽管这完全取决于上下文。
综上所述,确保内存使用量可预测非常重要。根据运行时环境(例如输入)允许草图使用任意数量的内存,迟早会引起问题。在某些情况下,这可能是完全安全的,例如,如果您知道使用量绝不会相加。草图可以在编程过程中更改。稍后更改某些内容时,可能会忘记早期做出的假设,从而导致无法预料的问题。
为了提高鲁棒性,通常最好在可能的情况下使用固定大小的缓冲区,并设计草图以从一开始就明确地处理那些限制。这意味着以后对草图的任何更改,或任何意外的运行时环境,都希望不会引起任何内存问题。
我不同意那些认为您不应该使用它或通常不必要的人的观点。我相信,如果您不了解它的来龙去脉,可能会很危险,但这很有用。在某些情况下,我不知道(也不应该知道)结构或缓冲区的大小(在编译时或运行时),尤其是涉及到我向世界发送的库时。我同意,如果您的应用程序仅处理单个已知结构,则应在编译时烘烤该大小。
示例:我有一个串行数据包类(一个库),可以接收任意长度的数据有效载荷(可以是struct,uint16_t数组等)。在该类的发送端,您只需告诉Packet.send()方法您要发送的内容的地址以及您希望通过其发送的HardwareSerial端口。但是,在接收端,我需要动态分配的接收缓冲区来保存传入的有效负载,因为该有效负载在任何给定时刻可能是不同的结构,例如,取决于应用程序的状态。如果我只是来回发送单个结构,则只需使缓冲区达到编译时所需的大小即可。但是,如果数据包的长度可能随时间变化而不同,则malloc()和free()并不是很糟糕。
我已经用以下代码运行了几天的测试,让它不断循环,但我发现没有内存碎片的迹象。释放动态分配的内存后,可用量将返回其先前的值。
// found at learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
uint8_t *_tester;
while(1) {
uint8_t len = random(1, 1000);
Serial.println("-------------------------------------");
Serial.println("len is " + String(len, DEC));
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("alloating _tester memory");
_tester = (uint8_t *)malloc(len);
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("Filling _tester");
for (uint8_t i = 0; i < len; i++) {
_tester[i] = 255;
}
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("freeing _tester memory");
free(_tester); _tester = NULL;
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
delay(1000); // quick look
}
我没有看到RAM出现任何形式的降级,也没有看到使用这种方法动态分配它的能力,所以我会说这是一个可行的工具。FWIW。
在Arduino上使用malloc()和free()真的不是一个好主意吗?
简短的答案是肯定的。以下是原因:
关键在于了解MPU是什么以及如何在可用资源的限制内进行编程。Arduino Uno使用带有32KB ISP闪存,1024B EEPROM和2KB SRAM 的ATmega328p MPU。那不是很多内存资源。
请记住,2KB SRAM用于所有全局变量,字符串文字,堆栈以及堆的可能用法。堆栈还需要留出一定空间以容纳ISR。
该内存布局是:
如今的PC /笔记本电脑的内存容量已超过1.000.000倍。每个线程1 MB的默认堆栈空间并不罕见,但在MPU上完全不现实。
嵌入式软件项目必须进行资源预算。这是在估计ISR延迟,必要的内存空间,计算能力,指令周期等。不幸的是,没有自由编程,而硬实时嵌入式编程是最难掌握的编程技能。
好的,我知道这是一个古老的问题,但是我越仔细地阅读答案,就越能回头再看一次似乎很重要的观察结果。
这里似乎与图灵的暂停问题有关。允许动态分配会增加上述“暂停”的可能性,因此该问题成为风险承受能力之一。尽管消除malloc()
失败的可能性等很方便,但这仍然是有效的结果。OP提出的问题仅与技术有关,是的,所使用的库的详细信息或特定的MPU确实很重要。对话将转向降低程序暂停或任何其他异常结束的风险。我们需要认识到容忍风险的环境大不相同。我的爱好项目是在LED灯条上显示漂亮的颜色,即使发生异常情况也不会杀死某人,但是心肺机内部的MCU可能会杀死某人。
对于我的LED灯带,我不在乎它是否锁定,我将其重置。如果我是通过MCU它的后果锁住或不操作控制的心脏心肺机上字面上生死,所以这个问题有关malloc()
,并free()
应该如何预定程序交易之间划分了展示先生的可能性图灵的著名问题。容易忘记这是一个数学证明,并且使自己相信,只要我们足够聪明,我们就可以避免成为计算极限的牺牲品。
这个问题应该有两个可以接受的答案,一个是针对那些盯着脸上的“停止问题”而被迫眨眼的人,另一个是针对所有其他人的。尽管arduino的大多数用途可能不是关键任务应用或生死攸关的应用程序,但无论您要编码哪个MPU,都仍然存在区别。
不,但是在释放分配的内存方面,必须非常小心地使用它们。我从来不明白为什么人们会说应该避免直接内存管理,因为这暗示了通常与软件开发不兼容的无能水平。
假设您使用arduino控制无人机。代码任何部分中的任何错误都可能导致其掉出天空并伤害某人或某物。换句话说,如果某人缺乏使用malloc的能力,那么他们很可能根本不应该编写代码,因为在很多其他领域中,小错误可能会引起严重的问题。
由malloc引起的错误难于跟踪和修复吗?是的,但这更多的是让编码人员感到沮丧而不是冒险。就风险而言,如果您不采取措施确保正确执行,则代码的任何部分都可能比malloc具有同等或更多的风险。