Unsure what is considered dynamic memory

I apologize for the probably (as usual) stupid question.
My understanding is that variables declared outside of functions are global and are stored in the program storage space. Does that include member variables of classes?

I have two classes. For testing the use of memory, I am including them in an empty .ino file that declares instances and then does nothing. I am not deliberately dynamically allocating/deallocating memory anywhere (malloc).

The whole thing compiles, but the dynamic memory use is unexpectedly large. I have added up the size of all the member variables in the classes (even though they are declared outside of functions and should be global?), and it's nowhere near the amount the compiler reports, even though there are quite a few unsigned long variables.

Sketch uses 1356 bytes (0%) of program storage space. Maximum is 253952 bytes.
Global variables use 674 bytes (8%) of dynamic memory, leaving 7518 bytes for local variables. Maximum is 8192 bytes.

And if I assume that all my member variables --assigned outside of functions and thus presumably in program memory -- are somehow being placed in dynamic memory, I get a much higher value: 11,000 bytes, more than the dynamic memory space!

One hypothesis: are the return value from functions counted as dynamic memory, since memory has to be allocated when they return a value?

The code is long, but I'll post it here anyway, in case anyone has the time to look through this.

//PollingTimer.h
#ifndef PollingTimer_h
#define PollingTimer_h
#include <Arduino.h>

class PollingTimer{
  public:
    enum TimeUnits {_micros, _millis, _seconds, _minutes, _hours};
    PollingTimer();
    void reset();
    void startTimer();
    void stopTimer();
    void restartTimer();
    unsigned long readMicros();   //limited by rollover of micros() every ~71.6 minutes 
    unsigned long readMillis();   //limited by rollover of millis() every ~49.7 days
    unsigned long readSeconds();  //limited by rollover of millis() every ~49.7 days
    unsigned long readMinutes();  //limited by rollover of millis() every ~49.7 days
    unsigned long readHours();    //limited by rollover of millis() every ~49.7 days
    unsigned long read(int timeUnit);

  private:
    static const int numTimeUnits = 5;
    unsigned long startTime[numTimeUnits];
    unsigned long prevTime[numTimeUnits];  
    unsigned long currentTime[numTimeUnits];

    const unsigned long microsPerMilli = 1000;
    const unsigned long microsPerSec = 1000000;
    const unsigned long microsPerMin = 60000000;
    const unsigned long microsPerHour = 3600000000;  

    const unsigned long millisPerSec = 1000;
    const unsigned long millisPerMin = 60000;
    const unsigned long millisPerHour = 3600000; 

    const int secsPerMinute = 60;
    const int minsPerHour = 60; 

    bool running = false;
    void incrementSecMinHours();
};
#endif


//PollingTimer.cpp
#include "PollingTimer.h"

PollingTimer::PollingTimer(){
  reset();
}

void PollingTimer::reset(){
  for(int tUnit = _millis; tUnit <= _hours; tUnit++){
    startTime[tUnit] = prevTime[tUnit] = currentTime[tUnit] = 0;
  }
  running = false;
}

void PollingTimer::startTimer(){ //starts timer from 0
  startTime[_micros] = prevTime[_micros] = micros();
  startTime[_millis] = prevTime[_millis] = millis();

  running = true;
}

void PollingTimer::stopTimer(){
  currentTime[_micros]=micros();
  currentTime[_millis]=millis();
  running = false;
}

void PollingTimer::restartTimer(){ //restarts timer from previous time
  running = true;
}

unsigned long PollingTimer::readMicros(){
  if(running){currentTime[_micros] = micros();}
  return (currentTime[_micros] - startTime[_micros]);
}

unsigned long PollingTimer::readMillis(){    //rounded down
  if(running){currentTime[_millis] = millis();}
  return (currentTime[_millis] - startTime[_millis]);
}

unsigned long PollingTimer::readSeconds(){   //rounded down
  if(running){incrementSecMinHours();}
  return currentTime[_seconds];
}

unsigned long PollingTimer::readMinutes(){   //rounded down
  if(running){incrementSecMinHours();}
  return currentTime[_minutes];
}

unsigned long PollingTimer::readHours(){     //rounded down
  if(running){incrementSecMinHours();}
  return currentTime[_hours];
}

unsigned long PollingTimer::read(int timeUnit){
  unsigned long timeResult;
  if(running){
    currentTime[_micros] = micros();
    incrementSecMinHours();
  }
  if (timeUnit == _millis){timeResult = (currentTime[_millis] - startTime[_millis]);}
  else if (timeUnit == _micros){timeResult = (currentTime[_micros] - startTime[_micros]);}
  else{timeResult = currentTime[timeUnit];}

  return timeResult;   
}

void PollingTimer::incrementSecMinHours(){
  currentTime[_millis]=millis();
  if((currentTime[_millis] - prevTime[_millis]) >= millisPerSec) {
    currentTime[_seconds] = currentTime[_seconds] + (int) ((currentTime[_millis] - prevTime[_millis]) / millisPerSec);
    prevTime[_millis] = currentTime[_millis];
    if((currentTime[_seconds] - prevTime[_seconds]) >= 60){
      currentTime[_minutes] = currentTime[_minutes] + (int) ((currentTime[_seconds] - prevTime[_seconds]) / secsPerMinute);
      prevTime[_seconds] = currentTime[_seconds];
    }
    if((currentTime[_minutes] - prevTime[_minutes]) >= 60){
      currentTime[_hours] = currentTime[_hours] + (int)((currentTime[_minutes] - prevTime[_minutes]) / minsPerHour);
      prevTime[_minutes] = currentTime[_minutes];
    }
  }
}

//PollingDelay.h
#ifndef PollingDelay_h
#define PollingDelay_h
#include <Arduino.h>
#include <PollingTimer.h>

class PollingDelay{
  private:
    //enum DelayMode {_micros, _millis, _seconds, _minutes, _hours};
    PollingTimer pollingTimer;                                                            //DUPLICATE
    enum TimeUnits {_micros, _millis, _seconds, _minutes, _hours};
    int delayMode;                                                                        //DUPLICATE
    unsigned long delayTime;    //in μs                                                   //DUPLICATE

    bool running = false;
    bool justFinishedFlag = false;                                                        //DUPLICATE
    const unsigned long microsPerMilli = 1000;
    const unsigned long microsPerSec = 1000000;
    const unsigned long microsPerMin = 60000000;
    const unsigned long microsPerHour = 3600000000;  

    const unsigned long millisPerSec = 1000;
    const unsigned long millisPerMin = 60000;
    const unsigned long millisPerHour = 3600000; 
    const int secsPerMinute = 60;
    const int minsPerHour = 60; 
  public:
    PollingDelay();
    void startDelayMicros(unsigned long delayTimeMicros);     //limited by rollover of micros() every ~71.6 minutes 
    void startDelayMillis(unsigned long delayTimeMillis);     //limited by rollover of millis() every ~49.7 days
    void startDelaySeconds(unsigned long delayTimeSeconds);   //limited by rollover of millis() every ~49.7 days
    void startDelayMinutes(unsigned long delayTimeMinutes);   //limited by rollover of millis() every ~49.7 days
    void startDelayHours(unsigned long delayTimeHours);       //limited by rollover of millis() every ~49.7 days
    bool isRunning();
    bool justFinished();
    void reset(); 
    unsigned long PollingDelay::read(int tUnit); 
};

#endif



//PollingDelay.cpp
#include "PollingDelay.h"

PollingDelay::PollingDelay(){
  reset();
}

void PollingDelay::startDelayMicros(unsigned long delayTimeMicros){
  this->delayTime = delayTimeMicros;
  delayMode = PollingTimer::_micros;
  pollingTimer.reset();
  pollingTimer.startTimer();
  running = true;
}

void PollingDelay::startDelayMillis(unsigned long delayTimeMillis){
  this->delayTime = delayTimeMillis;
  delayMode = PollingTimer::_millis;
  pollingTimer.reset();
  pollingTimer.startTimer();
  running = true;
}

void PollingDelay::startDelaySeconds(unsigned long delayTimeSeconds){
  this->delayTime = delayTimeSeconds;
  delayMode = PollingTimer::_seconds;
  pollingTimer.reset();
  pollingTimer.startTimer();
  running = true;
}

void PollingDelay::startDelayMinutes(unsigned long delayTimeMinutes){
  this->delayTime = delayTimeMinutes;
  delayMode = PollingTimer::_minutes;
  pollingTimer.reset();
  pollingTimer.startTimer();
  running = true;
}

void PollingDelay::startDelayHours(unsigned long delayTimeHours){
  this->delayTime = delayTimeHours;
  delayMode = PollingTimer::_hours;
  pollingTimer.reset();
  pollingTimer.startTimer();
  running = true;
}

bool PollingDelay::isRunning(){
  if(running){
    if(pollingTimer.read(delayMode) >= delayTime){
      pollingTimer.reset();
      pollingTimer.stopTimer();
      running = false;
      justFinishedFlag = true;
    }
  }
  return running;
}

bool PollingDelay::justFinished(){
  if(!isRunning() && justFinishedFlag){
    justFinishedFlag = false;
    return true;
  }
  else {return false;}
}

void PollingDelay::reset(){
  delayTime = 0;
  running = justFinishedFlag = false; 
  pollingTimer.stopTimer();
  pollingTimer.reset();
}

unsigned long PollingDelay::read(int tUnit){
  return pollingTimer.read(tUnit);
}  


//PollingDelay.ino
#include <string.h>
#include "PollingDelay.h"

PollingDelay microDelay;
PollingDelay milliDelay;
PollingDelay secDelay;
PollingDelay minDelay;
PollingDelay hourDelay;

void setup() {
}

void loop() {
}

PollingDelay/.ino isn't doing anything except declaring several instances of PollingDelay, each of which contains a member of type PollingTime.

After scouring the code and doing the math according to my (clearly incorrect) understanding of program memory vs. dynamic memory, I can't figure out where the compiler is getting it's values.

Somehow I am looking at this wrong. Can anyone help me with my understanding of what is being placed in program vs. dynamic memory?

They are certainly global, but if they were stored where you say then would their value be able to be changed by the sketch ?

In the past there was only RAM and ROM and the processor had registers.
Is it okay if I call them "SRAM" and "Flash memory" ?
Adafruit has a tutorial: https://learn.adafruit.com/memories-of-an-arduino

SRAM = RAM, data memory, dynamic memory, static ram, the processor can read and write from it.
It contains all the variables, the stack, the variables on the stack, the heap. Global variables have a fixed section here and local variables are often on the stack. There are variables that will get their initialized values during startup. All the text is often here as well. It is a lot.

Flash memory = ROM, program memory, it cannot be changed.
The code of the sketch is here. Normal processors have the 'const' variables here. The Arduino Uno has PROGMEM variables here. There is not much else going on here.

Which Arduino board do you use ?
Your data will be in SRAM, local or global or allocated on the heap, it is all in SRAM.

1 Like

Libraries are essentially classes which will have global memory allocated for their class variables. The IDE includes some libraries by default (depending on what features you use) that add to the global allocation.

1 Like

I don't really know. I don't understand how it works. Hence the question.

Ok, this makes things much more clear. I was confused by this description:

Dynamic memory is a term given to a concept which allows programmers to create and destroy persistent storage space at runtime. One of the major differences separating dynamic memory allocations from global variables is the life-time of the data.

http://arduino.land/FAQ/content/4/26/en/how-to-use-dynamic-memory.html#:~:text=Dynamic%20memory%20is%20a%20term,life-time%20of%20the%20data.

That description draws a distinction between global variables and dynamic memory...when in fact BOTH are stored in SRAM, and both are referred to in total as dynamic memory by the compiler. Confusing.

Are functions that return a value counted as variables during compile? The return value will require a space in memory, and the amount of that space is known at compile time.

Basically, what still doesn't make sense to me is this: aside from consts, Polling Timer mostly contains fifteen unsigned long variables (startTime[], prevTime[], currentTime[]), which is 60 bytes.

PollingDelay creates one instance of Polling Timer, and has almost no other data other than code.

PollingDelay.ino creates five instances of PolingDelay, which should be ~60*5 = 300 bytes. The compiler reports 674 bytes. Is the difference really just my code?

I have not heard of arduino.land before, and the text is wrong. Please forget that.
That website is a personal website and is not related to this official Arduino website.
There are good personal website, a great contributor to Arduino is Nick Gammon: http://www.gammon.com.au/forum/bbshowpost.php?bbtopic_id=123

It is not confusing. All the data that can change is in SRAM.
When a function returns a variable, that data has to be stored somewhere. It is stored on the stack, which is of course in SRAM.

Yes, the difference is your code, but the compiler is smarter than you think. The compiler can sometimes see that a variable or code is not used and then it does not compile that. The compiler can decide to mimic a standard function with inline code. Some libraries eat a lot of SRAM by only including them (and not even using any class or function of that library). The compiler can calculate complex calculation during compile time. If you want something confusing, then trying to understand the optimizations by the compiler is confusing :dizzy_face:

[ADDED]
After thinking a lot about that text, I think it is about writing a sketch. When a variable is needed throughout all the sketch all the time, then make it global (in SRAM of course). When a block of memory is needed for a short time, then allocate it from the heap (in SRAM of course).
However, it does not work that way. Below the surface is much more going on, and the heap is used more than you think. It is normal to allocate a block from the heap during startup and never release that memory.

1 Like

Does anyone else see the irony (the Alanis Morrisette variety) of implementing "dynamic" memory in static RAM?

No?
Just me then! :smile:

No worse than const variables

Would it be "silicony"? Also why does static RAM come in anti-static tubes? Also ROM are also random access, maybe should be a RAROM?

i always thought that they were the variables defined within a function that are dynamically allocated on the stack when the function is invoked. there could be many copies of those variable if the function is called recursively (by itself).

there would only be a single instance of a static variable defined within a function whose value would be persistent between invocations of the functions

there would only be a single copy of global variables accessible from anywhere in the program

and there could be static variable within a file with the same name as a global that is separate from the global variable

1 Like

Ok, and all of those are stored in SRAM.

That is in fact part of what confused me. Why is it called static RAM?

where else can variables be stored?

So, when the you compile a sketch and the compiler output says, "Global variables use n bytes (x%) of dynamic memory, leaving y bytes for local variables."

Does "dynamic memory" here mean SRAM?
Are "global variables" all variables that are declared outside of functions?
Would "local variables" be variables declared inside of functions?
And why is SRAM (static RAM?) being referred to as "dynamic memory".

I'm even more confused than before.

I thought the code was stored in flash memory. Why would that be affecting the compiler's report of how much "dynamic memory" my program is using?

What I'm trying to understand, and still isn't clear, is why a class (PollingTimer) that contains approximately 70 bytes of variables is compiling to use 253 bytes of dynamic memory. The .ino used to compile it is just a shell that creates an object of the class type.

And then, on top of that, I have a second class (Polling Delay) that uses 5 instances of the first class. It should use 5*253=1265 bytes of dynamic memory...but no, it uses 596 bytes. (!!???!??!)

Where are those extra bytes coming from?

Sketch uses 3298 bytes (10%) of program storage space. Maximum is 32256 bytes.
Global variables use 392 bytes (19%) of dynamic memory, leaving 1656 bytes for local variables. Maximum is 2048 bytes.

i don't know why the IDE uses those terms the way is does.

the Atmel 328p processor has 2 types of memory: Flash and SRAM. (looks like it uses Flash for EEPROM).

"program storage" is obvious Flash and user variables are in SRAM.

it knows about global and static variables that are allocated at compile time and describes them as "Global" leaving the remaining SRAM (max 2048) for what i described as dynamic variables or "malloc'd" variables

1 Like

Because it isn't dynamic RAM.
From a hardware POV, static RAM (SRAM) will maintain state without refresh, unlike dynamic RAM (DRAM).
DRAM is simpler, and denser, but has to be periodically refreshed.

1 Like

I see. So it's dynamic memory because it can be changed during runtime, but it lives on static RAM because the RAM doesn't need refresh cycles?

Basically different domains for the use of static and dynamic?