Variables scope

Hi, I'm having a doubt about declaring a variable inside the loop() function or declaring it as global.
I know what happens if it's defined as global or inside the funtion. But what I want to know is if I declare the variable inside the loop function the variable will be initialized in every loop, i.e. more load to the processor?

void loop(){
    int var_a;
    var_a = analogRead(A1);
}

In that code, the variable var_a is created in each loop?

But what I want to know is if I declare the variable inside the loop function the variable will be initialized in every loop, i.e. more load to the processor?

No, there is not additional load on the processor. The variable name is replaced by an address by the time the code is executed. The address is on the stack, not in the heap. Since the stack is continuously reused, the variable can not be accessed outside the function, since function calls use the stack.

The variable is NOT initialized when it is declared, since you failed to provide an initial value. It is given a useful value by return value from the analogRead() call.

PaulS:
The variable is NOT initialized when it is declared, since you failed to provide an initial value.

Well… :wink:

What Mr. Grumpy Pants failed to say is that you don’t need to, nor should you, initialize variables if the initialized value would never be used. (See: Frequently Asked Questions) This might go against the grain for some PC programmers, where uninitialized variables will earn you a dirty look, but this is embedded development and so things are different.

By default, avr-gcc will initialize variables to zero (or the equivalent, depending on type), so giving them an explicit initial value of zero does nothing but consume flash storage. Furthermore, if the first thing you do after declaring your variable is give it a value through some other means (like analogRead()), the initialized value serves no purpose anyway.

But no, there is no significant overhead whether you declare the variable globally or in your function calls. (This is possibly obvious, but it bears mentioning: Not every line of C code has an equivalent set of instructions associated with it.) Afterall, the stack for loop() will be torn down and reconstructed just by allowing it to exit back to main(), and then get called again by the while() loop there, so there’s already some redundant stuff going on.

If you’re really worried about it (and you shouldn’t be without good reason), you could do this:

void loop () {
Top:
  ...
  ...
  ...
  goto Top;
}

Or if you’d like to avoid goto out of principle, you can use a while(1) instead. Depends on if you prefer excess braces or antiquated flow control statements.

On a similar note, in general, I like to avoid global variables except in one or more of the following cases:

  1. It would exist for the entire run-time anyway.
  2. It needs to have its value kept through iterations of loop() or main(). (Although a static declaration would also achieve this.)
  3. It needs to be accessed via ISRs, where you aren’t calling the function explicitly to pass in data by parameters.
  4. It has to be accessed in multiple functions, and no amount of redesign could reasonably be expected to change this. (One good example I can think of is creating an object in setup() that will be used in loop(), since you don’t ordinarily “own” main() to pass that data between them.)

But again, this is embedded development. It’s just part of the deal to jettison best practices in PC development in favor of more efficient approaches given constrained hardware.

So, besides the obvious point of declaring a variable globally or inside the function, it's almost the same for the processor, even if the function is called constantly in an infinite loop, isn't it?

SirNickity:
By default, avr-gcc will initialize variables to zero (or the equivalent, depending on type), so giving them an explicit initial value of zero does nothing but consume flash storage.

True for global variables only but NOT for local variables. Local variables should ALWAYS be initialized by the programmer before any use other than assignment!

SirNickity:
If you’re really worried about it (and you shouldn’t be without good reason), you could do this:

void loop () {

Top:
  …
  …
  …
  goto Top;
}




Or if you'd like to avoid goto out of principle, you can use a while(1) instead.

Please don’t advocate using goto, especially here where it isn’t required. Doing while(true) will achieve the same effect.

There is no large overhead in this example of making a local variable, as a test shows that the compiler can choose to implement that purely in a register, and thus it doesn’t allocate any memory at all.

Example code:

void loop(){
    int var_a;
    var_a = analogRead(A1);
}

void setup ()
  {
  }  // end of setup

Generated assembler:

000000e0 <loop>:
  e0:   8f e0           ldi     r24, 0x0F       ; 15   (1)
  e2:   0e 94 74 00     call    0xe8    ; 0xe8 <analogRead>   (4)
  e6:   08 95           ret   (4)

No stack space was allocated even, nor was the variable initialized with zero. That’s only 9 clock cycles there, which of course pale into insignificance compared to the 104 µS of time that the analogRead takes (1664 cycles). So don’t try to over-egg the pudding. Write simple clear code. And avoid goto. :wink:

Even a more elaborate example (which prints the result) doesn’t allocate any actual memory for the variable:

void loop(){
    int var_a;
    var_a = analogRead(A1);
    Serial.println (var_a);
}
000000c0 <loop>:
  c0:   8f e0           ldi     r24, 0x0F       ; 15   (1)
  c2:   0e 94 6b 00     call    0xd6    ; 0xd6 <analogRead>   (4)
  c6:   bc 01           movw    r22, r24   (1)
  c8:   86 e9           ldi     r24, 0x96       ; 150   (1)
  ca:   91 e0           ldi     r25, 0x01       ; 1   (1)
  cc:   4a e0           ldi     r20, 0x0A       ; 10   (1)
  ce:   50 e0           ldi     r21, 0x00       ; 0   (1)
  d0:   0e 94 1e 03     call    0x63c   ; 0x63c <_ZN5Print7printlnEii>   (4)
  d4:   08 95           ret   (4)

There is no STS instruction there, so the compiler just used registers. Don’t try to second-guess the compiler. It knows what it is doing.

Does anyone care that there's a difference between defining a variable and declaring a variable? The distinction can really make some concepts easier to understand (e.g., the extern keyword).

colo:
But what I want to know is if I declare the variable inside the loop function the variable will be initialized in every loop, i.e. more load to the processor?

To answer that specific question, as you can see from the code I posted above, the load on the processor is less, if anything, by using local variables, because it is easier for it to choose to use a register and not actually allocate a "real" memory location.

Thanks, that was the answer I was looking for.

It achieves exactly the same effect. I would be surprised it if even compiled down to different instructions. So, aside from not being wrapped in an extra set of comfy braces… why are we afraid of goto again? I agree it can be very effective at making spaghetti code, but this is one of the two cases where I think it makes sense. I don’t believe in shunning useful syntax that has no negative side effects, just because of cargo cult wisdom. But I do respect your point of view. :slight_smile: Sincerely, what’s the big deal?

lloyddean:

SirNickity:
By default, avr-gcc will initialize variables to zero (or the equivalent, depending on type), so giving them an explicit initial value of zero does nothing but consume flash storage.

True for global variables only but NOT for local variables. Local variables should ALWAYS be initialized by the programmer before any use other than assignment!

Huh… Are you sure?

Quoting from the avr libc FAQ:

Global and static variables are guaranteed to be initialized to 0 by the C standard. avr-gcc does this by placing the appropriate code into section .init4 (see The .initN Sections). With respect to the standard, this sentence is somewhat simplified (because the standard allows for machines where the actual bit pattern used differs from all bits being 0), but for the AVR target, in general, all integer-type variables are set to 0, all pointers to a NULL pointer, and all floating-point variables to 0.0.

(Interesting, but less relevant, details snipped.)

Now if some programmer “wants to make doubly sure” their variables really get a 0 at program startup, and adds an initializer just containing 0 on the right-hand side, they waste space. While this waste of space applies to virtually any platform C is implemented on, it’s usually not noticeable on larger machines like PCs, while the waste of flash ROM storage can be very painful on a small microcontroller like the AVR.

So in general, variables should only be explicitly initialized if the initial value is non-zero.

Note:
Recent versions of GCC are now smart enough to detect this situation, and revert variables that are explicitly initialized to 0 to the .bss section. Still, other compilers might not do that optimization, and as the C standard guarantees the initialization, it is safe to rely on it.

(All emphasis mine.)

This says nothing about (non-static) local variables specifically, but the closing paragraphs above the note, combined with the line in the first paragraph where it states “ALL integers …, ALL floats …, ALL pointers…” could be taken to mean… well… ALL variables.

Nick did include a snippet of disassembled output that shows no evidence of setting a default value, although that could easily be because the variable was never read before having a value assigned to it. That is well within the compiler’s purvue to optimize out its own useless initialization. Having never checked the case myself where a variable IS read (on AVR!!) without initialization first, I can’t say which is the case for local variables. If someone doesn’t beat me to it, I’ll try to remember to check tonight when I get home.

Alternatively, it could mean that memory is initialized to zero through non-code means. Will have to peruse the data sheet on that account…

Taking your second point first, I tried to prove that local variables are not initialized with not a huge amount of success.

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  }  // end of setup

void foo ()
  {
  long a, b, c, d, e, f;
  
  a = b + c + d + e + f;
  Serial.println (a);
  Serial.println (b);
  Serial.println (c);
  Serial.println (d);
  Serial.println (e);
  Serial.println (f);
  }
  
void loop ()
  {
  Serial.println ("loop");
  Serial.println (micros ());
  foo ();
  delay (1000);  
  }  // end of loop

The variables are printed as zero, which suggests they are initialized to zero. However the compiler warns:

sketch_dec06d.ino: In function 'void foo()':
sketch_dec06d.ino:11: warning: 'b' is used uninitialized in this function
sketch_dec06d.ino:11: warning: 'c' is used uninitialized in this function
sketch_dec06d.ino:11: warning: 'd' is used uninitialized in this function
sketch_dec06d.ino:11: warning: 'e' is used uninitialized in this function
sketch_dec06d.ino:11: warning: 'f' is used uninitialized in this function

So the warning suggests that they are just fortuitously zero, not by design.

I agree it can be very effective at making spaghetti code ...

This. Start teaching newbies to use goto and before you know it ... lots of spaghetti!

Using while, for, do leads to thinking in a structured way. Using goto leads to wooly thinking.

This is due ( maybe ) to the vars being spread over registers, and appears they are reset, using volatile I get:

-821007856
876085505
825438259
1744844341
27721984
-130649
loop
1003460
-821009394
825753857
875769904
1744842806
27721984
-130649
loop
2006988

Each time loop is encountered, the vars have a different value.
Going to read up on how the avr spreads large values accross multiple registers, it might have to clear to 0 for sanity.

I don't know that a volatile local variable is particularly meaningful, but at least you have shown that they are not necessarily always zero.

Interesting. I do wonder if the memory is cleared somehow on startup, so any address space could be expected to yield zero until that memory is set to something else. Then, it could be as random as it is on a PC.

Given the compiler warnings and the volatile results, I think you guys may be right that local (non-static) variables are not necessarily covered by the context of that FAQ entry. It’s a bit ambiguous in that regard. Good to know.

All global memory/static data is zero initialised, the stack is too, however the stack is dirtied long before you can get your hands on it.

Local variables are placed on the stack ( forget optimisation into registers for a sec ), this stack data could have contained variables from a previous function call, therefore the data inside is kind of random, maybe the last use of the ram location set it to zero and you are lucky, but it cannot be relied upon.

Its the perfect scenario for security vulnerabilities to occur.

… in general, all integer-type variables are set to 0 …

After reading that part of the manual I am pretty sure that refers to the subset being “Global and static variables”.

Local variables (not static ones) are simply created by reserving space on the stack, and that stack is not necessarily zero.

This test demonstrates that a local variable (array) is not necessarily zero:

void setup ()
{
  Serial.begin (115200);
  Serial.println ();
}  // end of setup

void loop ()
{
  Serial.println ("loop");
  int foo [100];
  for (int i = 0; i < 100; i++)
    Serial.println (foo [i]);
  delay (1000);  
}  // end of loop

Output:

loop
-2223808
1
12545
12853
13621
12344
13879
24833
12545
9010
-16128
256
5304
255
9219
26624
-9981
-23807
6
12288
-23808
1
12545
12853
13621
12344
13879
24833
12545
9010
-16128
256
5304
255
9219
26624
-9981
-23807

The point they were making in the LIBC manual was that you don't need to explicit set global variables to zero, and indeed doing so might increase program size, because it then generates code to set the variable to zero, whereas that entire block of memory is cleared to zero anyway as part of program initialization.

Got it, here is a nice proof of concept about stack variables, and how the memory is reused.

There are two function calls, one has an initialised value, the other doesn’t. This is a security nightmare scenario.

Tested on an Uno

void A(){
  uint64_t a = uint64_t( 0xaabbccdd ) << 32 | 0x11223344;
  Serial.print( "Pointer Address: " );
  Serial.println( (unsigned int )&a );
  Serial.print( "Lower 32: " );
  Serial.println( (unsigned long )a, HEX );
  Serial.print( "Upper 32: " );
  Serial.println( (unsigned long )(a >> 32), HEX );
}

void B(){
  uint64_t b;
  Serial.print( "Pointer Address: " );
  Serial.println( (unsigned int )&b );
  Serial.print( "Lower 32: " );
  Serial.println( (unsigned long )b, HEX );
  Serial.print( "Upper 32: " );
  Serial.println( (unsigned long )(b >> 32), HEX );
}

void setup (){
  Serial.begin (115200);
  A();
  B();
}

void loop () { }

pYro_65:
Local variables are placed on the stack ( forget optimisation into registers for a sec ), this stack data could have contained variables from a previous function call, therefore the data inside is kind of random, maybe the last use of the ram location set it to zero and you are lucky, but it cannot be relied upon.

Its the perfect scenario for security vulnerabilities to occur.

Uh oh! Before you know it, someone's going to install a homebrew channel on your quad-copter. :wink: