Lifecycle of a variable? declaration, initialization, deallocation within scopes

Hi,
declaration
I was wondering what happens to variables when they are initialized differently?

For example, whats the difference of "x" between these two routines?
Also which method is preferable and why?

"x" is continuously (re-)initialized inside of a loop:

void loop() {
  while (true) {
    int x = 1;  //after the first pass, what happens to the previous reference to "x"????
  }
}

"x" is initialized only once and then written to in a loop:

void loop() {
  int x;
  while (true) {
    x = 1; 
  }
}

Why ?

I'm relatively new to C and Arduinos, in the past i have had issues resulting in lockups. i can only assume i overwrote something in memory by accident. im aware that in the two examples above the variable changes scope. but was mainly wondering if there were any differences in processor overhead or if this was handled by some optimization in the compiler??

from some research, i saw that the compiler will sometimes detect that a variable is in a loop and attempt to optimize it.
im not at all familiar with this process but assume that the address in memory to x in the first example would not be continuously reassigned.

i hope to slowly adopt good programming habits as i learn. and this stood out as a mystery to me. i only ask to further my understanding of how C++ and the Arduino actually work.

good luck.

the key idea is somthing known a scope it applies in almost all programming languages.

For example

int x;//x has file scope , every thing in the file can use it aka global
void bill(){
int x; //everything int the function can use it 

for(.........){
int x;//  block only usable within the {} of the for loop

}

}

In the above example 3 totally different x's are created and x=x+1 will refer to the x with the smallest scope.

Mark

megaBlocks:
I'm relatively new to C and Arduinos, in the past i have had issues resulting in lockups. i can only assume i overwrote something in memory by accident. im aware that in the two examples above the variable changes scope. but was mainly wondering if there were any differences in processor overhead or if this was handled by some optimization in the compiler??

from some research, i saw that the compiler will sometimes detect that a variable is in a loop and attempt to optimize it.
im not at all familiar with this process but assume that the address in memory to x in the first example would not be continuously reassigned.

You seem to have the basic idea. The life of a variable depends how it is declared. Global or static local data are allocated at compilation time and initialised at program startup. Local automatic variables are created when execution reaches the statement that defines them, and destroyed when execution leaves the scope that they were defined in. Conceptually, 'created' means the memory is is allocated (i.e. on the stack) and any initialisation is performed; 'destroyed' means the reverse. With some compilers, variables defined in a loop would actually be allocated and deallocated at each iteration. Most compilers are smart enough to optimise the redundant deallocation/reallocation out. That just leaves initialisation/construction and destruction still to be performed. In the first example you showed, the variable x would have space allocated on the first iteration and then the initialisation x=1 would be performed. On subsequent iterations, only the initialisation would be performed. (Just as an extra complication, compilers are quite good at optimising out pointless code and you would probably find that in practice none of those variables would actually exist in the code - they would be optimised out completely. But if we pretend you were doing something useful with them in the body of the loop then they'd be allocated and initialised as described above.

Thank you Peter for that very insightful explanation. I see I have a lot of homework to do if/when i ever need super tight timing on my Arduinos.

For fun i made this image. It's pretty self explanatory... :

I cant say I'm sure why the loop takes longer when using globals as opposed to locals. I thought it would be the other way around. Could somebody please elaborate on why this would happen?? Or verify if this is actually the case and not that I've done something incorrect.

Thanks!!

Here's the sketch from that test.

int IO_TOGGLE = false;

const int IO_PIN = 13;
const int G_TRIGGER = 0;
const int L_TRIGGER = 1;

void setup() {
  //Serial.begin(9600);
  pinMode(IO_PIN, OUTPUT);
  pinMode(G_TRIGGER, OUTPUT);
  pinMode(L_TRIGGER, OUTPUT);
}

int G1; 
int G2; 
int G3; 
int G4; 
int Globally() {
  G1 = IO_TOGGLE;
  G2 = G1;
  G3 = G2;  
  G4 = G3;
  digitalWrite(IO_PIN, G4);
  return G4 + G3 + G2 + G1;
}


int Locally() {
  int L1;
  int L2;
  int L3;
  int L4;
  L1 = IO_TOGGLE;
  L2 = L1;
  L3 = L2;  
  L4 = L3;
  digitalWrite(IO_PIN, L4);
  return L4 + L3 + L2 + L1;
}

int l;
int j;
int loops = 10;
int jumps = 100;
void loop() {
  
  digitalWrite(L_TRIGGER, 1);
  for (l = loops; l > 0; l--) {
    for (j = jumps; j > 0; j--) {
      Locally();
    }
    IO_TOGGLE = !IO_TOGGLE;
  }
  digitalWrite(L_TRIGGER, 0);
  
  digitalWrite(G_TRIGGER, 1);
  for (l = loops; l > 0; l--) {
    for (j = jumps; j > 0; j--) {
      Globally();
    }
    IO_TOGGLE = !IO_TOGGLE;
  } 
  digitalWrite(G_TRIGGER, 0);

}

Scope.gif

In this case, it's because the local variables are mostly optimized away out of existence. The compiler notices that you don't do anything with L2, L3, and L4, and just return 4*L1, so it just computes that. Also, since you have relatively few variables and haevn't specified otherwise, it can keep them all in registers.

int Locally() {
  int L1;
 100:   0f 93           push    r16
 102:   1f 93           push    r17
  int L2;
  int L3;
  int L4;
  L1 = IO_TOGGLE;
  L2 = L1;
 104:   00 91 04 01     lds     r16, 0x0104     ;; L1 = IO_TOGGLE
 108:   10 91 05 01     lds     r17, 0x0105
  L3 = L2;    ;; L2, L3, L4  optimized away
  L4 = L3;  
  digitalWrite(IO_PIN, L4);
  return L4 + L3 + L2 + L1;
 10c:   8d e0           ldi     r24, 0x0D       ; 13
 10e:   60 2f           mov     r22, r16
 110:   0e 94 92 01     call    0x324   ; 0x324 <digitalWrite>
 114:   00 0f           add     r16, r16
 116:   11 1f           adc     r17, r17    ;;  2 *L1
 118:   00 0f           add     r16, r16 
 11a:   11 1f           adc     r17, r17    ;; 2* (2*L1) == 4*L1 == L1+L2+L3+L4
}
 11c:   c8 01           movw    r24, r16
 11e:   1f 91           pop     r17
 120:   0f 91           pop     r16
 122:   08 95           ret
00000124 <Globally()>:
int G2; 
int G3; 
int G4; 
int Globally() {
  G1 = IO_TOGGLE;
  G2 = G1;
 124:   60 91 04 01     lds     r22, 0x0104
 128:   80 91 05 01     lds     r24, 0x0105
 12c:   60 93 06 01     sts     0x0106, r22
 130:   80 93 07 01     sts     0x0107, r24
  G3 = G2;  
 134:   60 93 08 01     sts     0x0108, r22
 138:   80 93 09 01     sts     0x0109, r24
  G4 = G3;
 13c:   60 93 0a 01     sts     0x010A, r22
 140:   80 93 0b 01     sts     0x010B, r24
  digitalWrite(IO_PIN, G4);
 144:   60 93 0c 01     sts     0x010C, r22
 148:   80 93 0d 01     sts     0x010D, r24
  return G4 + G3 + G2 + G1;
 14c:   8d e0           ldi     r24, 0x0D       ; 13
 14e:   0e 94 92 01     call    0x324   ; 0x324 <digitalWrite>
 152:   20 91 0a 01     lds     r18, 0x010A
 156:   30 91 0b 01     lds     r19, 0x010B
 15a:   80 91 0c 01     lds     r24, 0x010C
 15e:   90 91 0d 01     lds     r25, 0x010D
 162:   28 0f           add     r18, r24
 164:   39 1f           adc     r19, r25
 166:   80 91 08 01     lds     r24, 0x0108
 16a:   90 91 09 01     lds     r25, 0x0109
 16e:   28 0f           add     r18, r24
 170:   39 1f           adc     r19, r25
 172:   80 91 06 01     lds     r24, 0x0106
 176:   90 91 07 01     lds     r25, 0x0107
 17a:   28 0f           add     r18, r24
 17c:   39 1f           adc     r19, r25
}
 17e:   c9 01           movw    r24, r18
 180:   08 95           ret

megaBlocks:
I see I have a lot of homework to do if/when i ever need super tight timing on my Arduinos.

The most important thing is understanding the underlying concepts of variable scope and lifetime and initialisation and the logical behaviour that they produce, which you seem to have done. Details about exactly what optimisations are being performed under the covers are only going to matter in extremely rare cases where you're trying to optimise individual lines of code. More usually, performance optimisation is more effective at the architectural level, by improving your algorithms, avoiding data copying and so on rather than making individual statements run faster.