【问题标题】:RAM usage wrong when using pointers to class使用指向类的指针时 RAM 使用错误
【发布时间】:2020-05-12 19:06:51
【问题描述】:

我不明白编译器是如何估算 RAM 使用量的。我制作了以下 MCVE。 编译后(Arduino nano ATMEGA328P, new bootloader),Arduino编译器显示的RAM使用错误。我比较了创建DataContainer 结构实例的两种方式,通过使用new(运行时,堆?是正确的名称)创建一个指针数组和调用构造函数,以及仅创建一个结构数组(编译时间?)。

#include <Arduino.h>

struct DataContainer {
    int values[100] = {0}; // init to zeros
    void FillRandom() {
        for (int k = 0; k < 100; k++)
            values[k] = random(0, 200);
    }
};

class ExampleClass 
{
public:
    DataContainer *data[20]; // Heap, runtime?
    // DataContainer data[20]; // or compile time

    ExampleClass() {
        for (int k = 0; k < 20; k++)
            data[k] = new DataContainer(); // not necessary when using: DataContainer data[20]
    }
};

// declare class
ExampleClass EC;

void setup() {
    // call constructor
    EC = ExampleClass();
    EC.data[1]->FillRandom(); // for run time
    // EC.data[1].FillRandom();  // for compile time
}

如果我比较编译器显示的 RAM 使用情况,我会看到以下内容。

案例1,使用DataContainer *data[20];

RAM:   [          ]   3.1% (used 63 bytes from 2048 bytes)
Flash: [=         ]   5.8% (used 1782 bytes from 30720 bytes)

案例2,使用DataContainer data[20];

RAM:   [==========]  195.9% (used 4013 bytes from 2048 bytes)
Flash: [          ]   3.9% (used 1194 bytes from 30720 bytes)

(我知道这不适合 Arduino,仅作为示例)。

有没有办法估计 RAM 使用情况?

【问题讨论】:

  • 编译器并不真正知道您在运行时动态分配了多少内存,它需要编译器通常需要的更多分析。它所能做的就是使用它拥有的信息来估计,即您在堆栈上创建一个对象,其大小为20 * 4 字节。我不知道它是如何计算“RAM”的,但是在运行您的程序时,堆栈上将占用至少 80 字节的内存(加上返回地址等的少量额外内存)。如果您创建一个 objects 数组,那么在堆栈上至少有 20 * (100 * 4),即 8000 字节。
  • 至于差异,也许您的 Arduino 模型(您没有告诉我们)使用的是 16 位 CPU,其中指针的 sizeof 以及 int 是每个 16 位(2 个字节)?那么它会更有意义。
  • 在小型嵌入式系统上,如 Arduino Nano,动态内存分配几乎总是会产生很多开销。您不仅需要内存用于对象本身,还需要内存用于指向对象的指针,以及内存分配器内部的内务管理和元数据。由于您只有 2KiB 的 RAM,因此您必须对变量和对象非常保守,并且必须从头开始基于此限制进行设计。
  • 我在为内存受限的设备编写操作系统方面拥有丰富的经验,我强烈建议您使用只有 2048 字节 RAM 的情况下使用 C 编程,而不是 C++。
  • @MichaëlRoy:是的。我强烈建议他们不要这样做。

标签: c++ object memory arduino


【解决方案1】:

不要在 16 位 MCU 上使用动态分配。尤其是在纳米上。只是没有足够的内存。

估计所需的 RAM 空间很简单。

  1. 计算结构的大小。

    结构数据容器 { 整数值[100] = {0}; //初始化为零 /* ... */ };

100 x sizeof(int) = 100 x 2 = 200。

  1. 计算数组的大小。

    DataContainer 数据[20];

20 x 200 = 4000 字节。

关于您的代码的一些注释......

// declare class
ExampleClass EC;   //  Good!  On small MCUs, global variables are the thing!
                   //  Note that the constructor HAS BEEN called.

void setup() {
    // call constructor
    EC = ExampleClass();    // ???  Why are you creating a new object???
                            // creating a 4000 bytes object on the stack 
                            // is a sure way to crash your app.  I give it
                            // no more than a couple of microseconds before 
                            // crashing on a nano.

                            // This line of code is not needed, EC is already 
                            // initialized.

                            // Always leave room on the stack, it's needed for 
                            // calling functions and also for interrupts.
    /* ... */
}

解释:这一行,EC = ExampleClass(); 在堆栈上创建一个临时对象,然后将其复制到全局变量 EC 中。

编译器之所以只指出4000字节的已用内存是因为栈空间不包括在计数中。您的代码需要 4000 字节的全局内存 RAM + 4000 字节用于堆栈上的临时变量,然后需要更多才能运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-16
    • 2015-09-10
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多