Why are local variables avoided on Arduino?

In several code examples for Arduino I notice that there is almost no use of variables in local scope. One of the examples present in the IDE: Analog > AnalogInput:

int sensorPin = A0;
int ledPin = 13;
int sensorValue = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  sensorValue = analogRead(sensorPin);
  digitalWrite(ledPin, HIGH);
  delay(sensorValue);
  digitalWrite(ledPin, LOW);
  delay(sensorValue);
}

The variable sensorValue is global, while its use is only within the function loop. Another case is the library Moving-Average-Filter. A part of the code:

#define MAX_DATA_POINTS 20

class MovingAverageFilter {
public:
  MovingAverageFilter(unsigned int newDataPointsCount);
  float process(float in);

private:
  float values[MAX_DATA_POINTS];
  int k;
  int dataPointsCount;
  float out;
  int i;
};

Here the members out and i are used only in process, and must be Local:

float MovingAverageFilter::process(float in) {
  out = 0;

  values[k] = in;
  k = (k+1) % dataPointsCount;

  for (i=0; i<dataPointsCount; i++) {
    out += values[i];
  }

  return out/dataPointsCount;
}

Use variables in this way seems absurd to me. Is that purposeful? If so, why?

The only possibility I can imagine is that the address of local variables would be known at compile time, so they can be accessed without taking into account the stack logger. Does it really make a difference?

But in the case of the Class, I can't see how it could be faster to read an object through the pointer this than to read it in the stack, relative to registered.

Another explanation might be to avoid having to allocate a stack frame to the function. But this allocation should be as simple as incrementing a registered one, I don't understand why it should be avoided. Also functions that receive arguments will have a stack frame anyway.

 20
Author: Maniero, 2013-12-24

3 answers

I researched on the subject and didn't find any good answers on it. The reasons I managed to raise are as follows:

  • Programs written for Arduino are generally quite simple and there is little memory available. As a result, few people care much about modularization and encapsulation in Arduino programs.

  • Arduino does not use a single main() method to run the program. Instead, it uses two methods setup() and loop() independent. The result of this is that for you to be able to use within loop() What was defined in setup(), you end up being forced to use global variables.

  • Often the variables used in the loop() method must be remembered between iterations. This causes you to end up being forced to use global variables.

  • Arduino is too simple and limited so you can use events, callbacks and messaging in a way effective.

  • Most examples are written for beginners who know little about C, so everything is somewhat oversimplified. In addition, a large part of Arduino users do not have much interest, practice or training in software programming, as their focus is on hardware.

Useful but inconclusive discussions can be found here and here.

Concluding: I recommend following the good traditional programming practices. Constants can be easily optimized by the compiler, so you can declare them in the global scope without problems (the problem of global variables is when the value is changed, which does not occur with constants). Anything that can change in value, you'd better stay in a local scope unless you have no choice. If you are going to use global scope, remember the modifier static to make the variable private and make functions getters available and setters if you need to export it.

 17
Author: Victor Stafusa, 2013-12-25 18:47:07

See what the Arduino website says:

" when programs start to get bigger and more complex, local variables are a useful way to ensure that only the function have access to your own variables. This avoids programming errors when one function inadvertently modifies variables used by another function. Sometimes it is convenient to declare and initialize variables within a for(...)." ( http://arduino.cc/en/Reference/scope )

Indiscriminate use of global variables is not advised; they should be used only when it is necessary to access their values from different blocks. Use local constants or variables whenever possible.

 6
Author: Allan Denis, 2014-01-29 15:11:31

What happens is that variables defined within a scope are allocated in the function frame (in the stack).

Variables defined in the global go straight to the data segment.

The difference is that while accessing data from the data segment uses direct addressing, to access data allocated in the frame is accessed by indexed indirect addressing.

For those who understand ASM Z80, it's something like (AVR uses RISC, that's not exactly what happens in the Apr):

Direct addressing:

LD A, (VAR_ADDRESS)

Indexed indirect addressing:

LD HL, stack_pointer
LD A, (HL),VAR_INDEX

Obvious that the first form is faster, that is, it consumes fewer processor cycles. Even if there is an AVR intrusion that does everything at once, it still hits the memory twice: one to pick up the base, another to pick up the data (whose address is calculated from the base, which in itself also spends processor cycles).

So if you are programming a time critical algorithm, every processor cycle counts and you'll want to generate the fastest possible instruction. Most of the time, this gain is marginal but time or time again it makes a difference.

An alternative to global variables is to use "static" scope variables. They are allocated in the data segment equally, but only the scope where it was defined is able to address it:

float MovingAverageFilter::process(float in) {
  static float out = 0;

  values[k] = in;
  k = (k+1) % dataPointsCount;

  static int i;
  for (i=0; i<dataPointsCount; i++) {
    out += values[i];
  }

  return out/dataPointsCount;
}

A side effect is that instead of having only one "i" in the universe, each function that declare your static int i to your counters will allocate an address in the data Segment-10 functions, 10 variables allocating space.

So if your RAM space is getting short, it is better then to declare globally even to save space in the data segment.

Sometimes we need to give up good practices when dealing with limited hardware. When you have only 2k of RAM, spend 40 bytes with 10 32-bit counters being able to do the same service with only 4 bytes can be the difference between using a cheaper AVR, or having to use a more expensive one.

If you want to produce thousands of units of your product, a 1 USD more expensive chip will have cost thousands of USD more to do the same service.

EDIT: note that I solemnly ignored the compiler optimizations. A compiler could, if the counter was declared within the scope of the function (and use for (int i = 0....) is a way to help the compiler pull this out), it it could go straight to an AVR Registrar (it has 32!memory and processor cycles are saved-nothing is faster than using the recorder!

 2
Author: Lisias, 2015-10-17 01:19:35