3

In the past I declared variables inside the main loop which worked just fine. In a new project I did the same:

void loop(void)
{
    uint8_t counter;
    ....
    if (buttonPress)
        counter = 0;
    ...
    if (someCondition == true)
    {
        Serial.println(counter);
        counter++
    }
}

But then the output of the variable counter shows that for some reason the counter variable is reset to 0 instead of counting up. This happens even after the button is released.

The scope of the variable counter is the main loop and I just assumed that it will keep its value and count up.

What might be the reason for this variable to reset to 0? Is it that other functions are called within the main loop? Is it that an interrupt handler is called every now and then?

The solution I've found is to declare the variable as static:

static uint8_t counter;

Then it increments as expected.

Even though I've found a solution I'd really like to understand what the problem with my first approach was.

Michel Keijzers
  • 12,759
  • 7
  • 37
  • 56
Sören
  • 41
  • 3
  • 1
    This question fits better in stack overflow, since it is not related to Arduino. – Michel Keijzers Jun 30 '19 at 10:20
  • So the main loop in the Arduino just behaves as any regular function that is called? For some reason I thought that variables declared in main() are automatically static and keep their value. – Sören Jun 30 '19 at 10:27
  • 1
    No they are not, afaik the loop() is just a function that has a while(true) or while(1) inside. – Michel Keijzers Jun 30 '19 at 10:29
  • Thank you very much for the explanation and possible solutions. – Sören Jun 30 '19 at 10:35
  • Just to get this straight: With the arduino the main loop() just is a regular C-function with all the implications regarding variable scope. But in a C-program a variable declared in main() keeps its value. So if I used plain C for my program instead of the Arduino framework the *counter* variable would work without being declared *static*. Of course then the if-statements would be located in a while(1) loop. – Sören Jun 30 '19 at 11:00
  • Yes you are right (if I follow your sentences correctly). I'm not fully sure, but I guess the main() which is mandatory for c is created by the IDE and invisible for the user, it initializes something, than calls setup (once), than maybe some other checks are done, than a `while (true) { loop(); } is called, – Michel Keijzers Jun 30 '19 at 12:36
  • (btw if an answer is helpful, upvote it (by clicking the 'up' arrow), if an answer (or the best answer is given that solves your problem), accept the answer. – Michel Keijzers Jun 30 '19 at 12:38
  • 2
    @MichelKeijzers: Here is [`main()`](https://github.com/arduino/ArduinoCore-avr/blob/1.6.23/cores/arduino/main.cpp#L33). – Edgar Bonet Jun 30 '19 at 12:41
  • @EdgarBonet Thanks for that code. – Michel Keijzers Jun 30 '19 at 12:42
  • @Sören _"But in a C-program a variable declared in main() keeps its value."_ - A variable in `main()` keeps it value until execution leaves `main()`. Just as for Arduino's `loop()`, and indeed any other function. (One difference of course is, that once you leave `main()`, your program exits and it doesn't matter what happens to the variables.) – marcelm Jun 30 '19 at 22:28

3 Answers3

4

Michael's answer was good, as usual. Let me give you some more background though:

In C/C++ (And in most modern languages) variables have a "scope", or an area where they are defined.

Global scope:

Variables declared at the top level of your program have global scope, and exist for the life of your program. These variables are usually created on the .data or .bss sections.

Local scope

Variables declared inside a function have local scope. Unless they are static, they get created when the function is entered, and get discarded when the function exits. These variables are created on the stack as part of the function's stack frame.

Object scope

In C++ and other object-oriented languages, instances of objects have their own scope, "instance variables". Every instance of an object has its own set of instance variables. (Think of cars, and a car radio. You and I might own the exact same model of car, but if I set my car's radio station and you set your car's radio to a different station, each instance of the car has a different setting for the radio station.)

Static variables:

Static variables are variables that are declared "statically." That means that they are only created once and persist, even if they are declared inside a scope like a function or a class instance. These have the lifespan of a global variable but can be declared inside a function or in an object.


Loop is a function, so variables declared inside of loop() are local variables. They get created anew every time the function is called, and discarded every time the function exits.

Duncan C
  • 5,562
  • 3
  • 16
  • 28
2

Your code has a couple of bugs. First you don't initialize counter before using it. Second the variable 'counter' has limited scope, it has no definition when loop() exits. If you want this variable to stay around declare it as static.

Both bugs corrected e.g.:

void loop(void)
{
    static uint8_t counter = 0;
    ....
    if (buttonPress)
        counter = 0;
    ...
    if (someCondition == true)
    {
        Serial.println(counter);
        counter++
    }
}

Note: Initializing counter to 0 only happens once, on subsequent calls to loop() counter will retain it's previous value.

Jeff Wahaus
  • 573
  • 2
  • 5
0

The problem is that you declare a new variable. So even though the name is the same, the local variable is destroyed at the end of the loop and recreated at the beginning.

Actually, there is no good solution:

  • Global variables are best to be avoided. They can be seen as 'singleton's', e.g. variables that are needed at many places in the code and have only one occurrence.
  • As you found out, static solves the problem. I would use this option, although personally I'm not a fan of static variables.
  • Another way is to put the loop in a while, so you get:

    void loop(void) { uint8_t counter = 0; // Always initialize variables while (true) { // Your code } }

    This will work too, as your counter is only initialized once, however the loop is already (internally) a while loop, so you have two.

  • A much better way but more extra work, is to create a C++ class, where counter can be a class variable, which is local for that class (which could still be a singleton). Of course this is only true when the counter variable is belonging logically to that class, if it is not, just keep it a global variable (updated after Edgar Bonet's remark).

Michel Keijzers
  • 12,759
  • 7
  • 37
  • 56
  • Making the counter a property of an object which is itself a global variable would be no better than making the counter itself global. It would actually be worse: any complexity that serves no useful purpose is always a bad thing in a program. – Edgar Bonet Jun 30 '19 at 11:18
  • @EdgarBonet You are right when that variable (the counter) is unrelated to the class that would be created (I was assuming so, but I will update my answer). If the counter is unrelated, than you are absolutely right. – Michel Keijzers Jun 30 '19 at 12:33
  • Could you explain why simply using a global variable should be avoided? I see no disadvantage in terms of memory usage or performance. I'd say, using a global variable is just fine in many situations. In my opinion making it more complicate is also not very elegant. – Sim Son Jun 30 '19 at 12:39
  • @SimSon There is no disadvantage in terms of memory usage or performance, bit if you make all variables global and you have thousands of them, you can imagine what mess it will be. This will not happen easily in an Arduino Uno with 2 KB memory, but it's best to keep functionality isolated. Actually, one of the worst things about the Arduino IDE is that it works best with just an Ino file, and most people do not get further creating classes or separate files. – Michel Keijzers Jun 30 '19 at 12:41
  • @SimSon: See [Are global variables evil in Arduino?](https://arduino.stackexchange.com/questions/41493). – Edgar Bonet Jun 30 '19 at 12:42
  • @MichelKeijzers okay, so how to manage this kind of problem is more a personal decision. I'm quite used to having several globals and I think it's very readable (a huge program with "thousands" of global variables will be complex anyway). Thanks for clarification – Sim Son Jun 30 '19 at 12:52
  • @EdgarBonet thanks, I allready know this question. – Sim Son Jun 30 '19 at 12:52
  • @SimSon In your case, you can use the static, because you will use the variable only in that one function. Or keep it global (than you can use it in different functions too when you decide to split the loop function when it gets to big, or when you need it in an interrupt). In the program I'm making I have classes with variables inside classes (called properties) but my program is over 1,000 lines. – Michel Keijzers Jun 30 '19 at 12:54
  • @MichelKeijzers I'll keep that in mind, thank you! – Sim Son Jun 30 '19 at 12:57
  • Btw, there is a disadvantage of using not global variables, as Duncan C mentioned, global variables to to the heap, and local variables to a stack. That means they will not be counted in the summary you get when you build your sketch (so you have to manually keep track of the local variable size (and parameters) to know if you can run into memory problems. – Michel Keijzers Jun 30 '19 at 12:58
  • Globals are statically allocated into the .data or .bss sections, just like `static` locals. They are all counted in the build-time memory usage summary. The heap is where you get with `malloc()` and `new`. – Edgar Bonet Jun 30 '19 at 13:09
  • @EdgarBonet You are fully right (again) ... in the case of the OP both global and static will be counted towards .data/.bss, and local (non static) go to stack. – Michel Keijzers Jun 30 '19 at 18:02
  • I don't like the "loop in a while" idea. The `void loop(void)` function is already called from a loop, which can be presumed to have some other code in it until we know otherwise. The "loop in a while" idea prevents that other code from running because it never returns. – AaronD Jul 01 '19 at 03:30
  • @AaronD True ... actually it does not even work as Edgar Bonnet shows there is some code called in the framework after each call to loop. – Michel Keijzers Jul 01 '19 at 08:44