[Solved]The 'mechanics' behind the static keyword

I know that a static variable is remembered between consecutive calls to a function.

I always thought that a static variable was initialized at compile time. This however does not seem to be the case.

The below code will always print the initial state of the button, even if the button is pressed or released.

void setup()
{
  Serial.begin(9600);
  Serial.println("Entering setup");
  pinMode(2, INPUT_PULLUP);
  delay(3000);
}

void loop()
{
  Serial.println("Entering loop");

  static byte buttonState = digitalRead(2);
  Serial.println(buttonState);
  delay(500);
}

Can somebody please explain how / when static variables are initialized. From the output of the above, 'buttonState' seems to be initialized in the first run of loop().

// Edit to mark as solved

Thanks for the reply; I did figure that out already (hence the println() and delay() in setup() :wink:

What I like to know is the 'mechanics'. The assembly code now seems to have to keep a flag that it has (or has not) initialized the variable. That would mean wasting memory and cpu cycles.

It would translate to something like (taking into account that the processor has no idea about scope of variables)

byte buttonState;
bool __buttonState_initialized = false;


void setup()
{
  ...
  ...
}

void loop()
{
  if (__buttonState_initialized == false)
  {
    buttonState = digitalRead(2);
  }
  ...
  ...
}

Would it also work like this if I used a static variable that is initialized to a hard coded value (LOW in this case)? Or if I use a hardcoded value, is the initialisation done before loop() (or main() for that matter) is even called?

Or would it translate to something like below where init() initializes the variable once-off.

byte buttonState;

init()
{
  ...
  ...
  buttonState = LOW;
  ...
  ..

  main();
}

main()
{
  setup();
  for(;;)
  {
    loop();
  }
}

setup()
{
  ...
  ...
}

loop()
{
  // no initialisation of buttonState as it is already done

  // other code
}

sterretje:
What I like to know is the 'mechanics'.

Variables can live in three places:

  1. In a machine register (ignore this :slight_smile: )
  2. In an allocated block of memory that "lives" permanently for as long as the program is loaded
  3. on the stack.

EDIT: oh, make that 4
4) on the heap.

sterretje:
The assembly code now seems to have to keep a flag that it has (or has not) initialized the variable.

Why do you believe that is the case?

PaulMurrayCbr:
Variables can live in three places:

  1. In a machine register (ignore this :slight_smile: )
  2. In an allocated block of memory that "lives" permanently for as long as the program is loaded
  3. on the stack.

EDIT: oh, make that 4
4) on the heap.

Not quite sure how it helps me to understand the 'mechanics'

Any other way of knowing if buttonState is assigned a value or not?

sterretje:
Any other way of knowing if buttonState is assigned a value or not?

That's the wrong question. If done correctly there is no need to know if buttonState has been initialized. {However, your bigger question (How is buttonState initialized?) is most excellent!}

AVR Libc has a mechanism by which code can run before main is called. The compiler generates code that initializes buttonState. The linker places that code in the image so it runs before main.

Every C(++) toolset I have ever used works that way.

When you declare something as static, it will be stored in the same place as the global variables. If some badly-behaved function runs off the end of its assigned static memory space, it could damage a static variable of an unrelated function. Or it could damage a global. The C language does not have any protection for those kinds of errors.

So how does it "know" that it has already been assigned its initial value? I don't think it is productive to look at the problem in that way. The optimizing compiler has a lot of freedom to rearrange your program into any order. You should expect it will do this in a way that makes your program work better, while still preserving the same logic.

Google "crt0"

I always thought that a static variable was initialized at compile time. This however does not seem to be the case.

The below code will always print the initial state of the button, even if the button is pressed or released.

  static byte buttonState = digitalRead(2);

How could the variable be initialized at compile time? You can't get the results from digitalRead at compile time.

It would be more accurate to say that the variable is initialized once, the first time that that line of code is processed.

I started this thread with

I always thought that a static variable was initialized at compile time.

And

Can somebody please explain how / when static variables are initialized

I know how I would structure an assembly program to do this (but the initialisation of the variable would definitely not be in loop() in that case).

I indeed don't need to know as longs as it's done; but the (assembly) code must have something to know if it has initialized the variable or not.

And that is exactly what I like to know; what does that code look like (in relation to other parts of the code). Because it basically means that the buttonState is not initialised in loop() but beforehand (contrary to earlier replies saying "the first time loop() runs"). And it must be initialised after the pinMode is set.

I know; that is why I asked the question. As I wrote, "I always thought ... This however ..." and "Can somebody please explain..."

AWOL:
Google "crt0"

I have done that before but I just understand the basic idea. ctr0 is, to my knowledge, not compiled when a program is compiled; it exists as an object file that will be linked in. If it's indeed crt0 that takes care of this, it is created on the fly at compile time based on the actual code that one wrote; I doubt that as I have never seen crt0 being compiled / assembled in the output window of the IDE. I don't even see a reference for it being linked in the verbose compile output.

Unfortunately my knowledge of AVR assembly is basically non-existent (in the process of trying to improve). I did manage once to get out a mix of assembly and C/C++ source code using some command but have forgotten the command :frowning:

sterretje:
And that is exactly what I like to know; what does that code look like (in relation to other parts of the code). Because it basically means that the buttonState is not initialised in loop() but beforehand (contrary to earlier replies saying "the first time loop() runs"). And it must be initialised after the pinMode is set.

Bummer. Looks like a hidden flag is used rather than crt0...

void loop()
{
  Serial.println("Entering loop");
 14a:	6f e0       	ldi	r22, 0x0F	; 15
 14c:	71 e0       	ldi	r23, 0x01	; 1
 14e:	80 e5       	ldi	r24, 0x50	; 80
 150:	91 e0       	ldi	r25, 0x01	; 1
 152:	0e 94 5e 04 	call	0x8bc	; 0x8bc <_ZN5Print7printlnEPKc>

  static byte buttonState = digitalRead(2);
 156:	80 91 3f 01 	lds	r24, 0x013F
 15a:	81 11       	cpse	r24, r1
 15c:	08 c0       	rjmp	.+16     	; 0x16e <loop+0x24>
 15e:	82 e0       	ldi	r24, 0x02	; 2
 160:	0e 94 09 02 	call	0x412	; 0x412 <digitalRead>
 164:	80 93 3e 01 	sts	0x013E, r24
 168:	81 e0       	ldi	r24, 0x01	; 1
 16a:	80 93 3f 01 	sts	0x013F, r24
  Serial.println(buttonState);
 16e:	4a e0       	ldi	r20, 0x0A	; 10
 170:	50 e0       	ldi	r21, 0x00	; 0
 172:	60 91 3e 01 	lds	r22, 0x013E
 176:	80 e5       	ldi	r24, 0x50	; 80
 178:	91 e0       	ldi	r25, 0x01	; 1
 17a:	0e 94 cc 04 	call	0x998	; 0x998 <_ZN5Print7printlnEhi>
  delay(500);
 17e:	64 ef       	ldi	r22, 0xF4	; 244
 180:	71 e0       	ldi	r23, 0x01	; 1
 182:	80 e0       	ldi	r24, 0x00	; 0
 184:	90 e0       	ldi	r25, 0x00	; 0
 186:	0c 94 32 01 	jmp	0x264	; 0x264 <delay>

Register r24 = whatever is stored at address 0x013F. 0x013F was initialized to zero by crt0 so the first time here r24 = 0.

  156:	80 91 3f 01 	lds	r24, 0x013F

If r24 is zero skip the next instruction (the rjmp).

  15a:	81 11       	cpse	r24, r1

Skip the next five instructions.

  15c:	08 c0       	rjmp	.+16     	; 0x16e <loop+0x24>

Call digitalRead then store the result at address 0x013E (buttonState).

  15e:	82 e0       	ldi	r24, 0x02	; 2
  160:	0e 94 09 02 	call	0x412	; 0x412 <digitalRead>
  164:	80 93 3e 01 	sts	0x013E, r24

Store a 1 at address 0x013F so the next time here the code above is skipped.

  168:	81 e0       	ldi	r24, 0x01	; 1
  16a:	80 93 3f 01 	sts	0x013F, r24

Maybe they should have called it 'stasis' or something.....rather than static. But, it looks like...depending on the usage of 'static'.... it can have different unintuitive meanings.

Ah, now I understand. The following is valid C++ (the whatever function is the important part)...

void setup()
{
  Serial.begin(9600);
  Serial.println("Entering setup");
  pinMode(2, INPUT_PULLUP);
  delay(3000);
}

void whatever( uint8_t const pin )
{
  static byte buttonState = digitalRead( pin );
  Serial.println(buttonState);
}

void loop()
{
  Serial.println("Entering loop");
  whatever( 13 );
  delay(500);
}

The only way that can work is to initialize on first call. A deep compile-time analysis is essentially impossible (too expensive) in C++ so the toolset cannot know at compile-time that the first call is whatever(13) which precludes using crt0 for the initialization. Nasty.

Sorry about lying to you @sterretje. I promise, some things really are initialized by crt0. I assumed one more than reality.

So, it's both a memory and a performance hit.
Interesting,

Thanks, I did find the command back (avr-objdump -S) and found the same instructions in loop (my loop for this only contained the variable declaration with the digitalRead()).

void loop()
{
  static byte buttonState = digitalRead(2);
  fa: 80 91 01 01 lds r24, 0x0101
  fe: 81 11       cpse r24, r1
 100: 08 c0       rjmp .+16     ; 0x112 <loop+0x18>
 102: 82 e0       ldi r24, 0x02 ; 2
 104: 0e 94 ec 00 call 0x1d8 ; 0x1d8 <digitalRead>
 108: 80 93 00 01 sts 0x0100, r24
 10c: 81 e0       ldi r24, 0x01 ; 1
 10e: 80 93 01 01 sts 0x0101, r24
 112: 08 95       ret

So indeed, it keeps a flag.

Found something else interesting. The compiler indeed optimizes

void setup()
{
  pinMode(2, INPUT_PULLUP);
  ea: 62 e0       ldi r22, 0x02 ; 2
  ec: 82 e0       ldi r24, 0x02 ; 2
  ee: 0e 94 b3 00 call 0x166 ; 0x166 <pinMode>
  pinMode(3, INPUT_PULLUP);
  f2: 62 e0       ldi r22, 0x02 ; 2
  f4: 83 e0       ldi r24, 0x03 ; 3
  f6: 0c 94 b3 00 jmp 0x166 ; 0x166 <pinMode>

000000fa <loop>:
}

I started with only the first pinMode function and was very confused that it used a jmp and not some form of call. Added a second pinMode to see what happened.

The one is a call, the other one a jmp. Indeed some clever optimizing as the return from the second function will take you straight back to the function that called setup().

Everybody, thanks for the help.