When a static variable goes wrong

Sharing my experience and thoughts:

I had this code in my project:

void loop() {
    static EthernetClient client = server.available();

I have just added this "static" there, based on these thoughts:
"my stack is pretty small! I do not know how much memory client would need from stack. And not doing all the time this function call could speed up the code (reuse when it is already set, not like a temporary, auto variable which has to be initialized on every iteration in this loop())".

But nothing on my HTTP server (EthernetClient) was working anymore: no compile error, not a crash, just client was always zero and never acting on WebBrowser connected to it.

Obvious!

"static" can have two flavors:

  • You define a variable as static and you assign a value to it:
    This is OK. Behind the scene the value for this static variable has a copy in ROM (Flash) and will be initialized during startup code executed. The .bss section (see linker script) has all the values to write to static variables before it jumps to main(). So, the variable is initialized, way before you would execute the function, here loop().

  • But when you assign a value to a static variable with the result of the function call - the question is: "when is this function executed?" (and return value assigned)

So, the difference is: to assign the return value of a function call to a static variable needs the execution of this function. "But when will it be executed?"

Potentially, also during the startup code: way before the main(), well before the loop() is called. The function is called "so early" that the function call itself is "too early".
Potentially, this function needs to have the ETH device configured, the network is up and running. But all this is done inside my main(), or the setup() function.
The compiler plus linker plus startup code organizes the code in a way that it will call this function to assign the result to static "soo early" that nothing is ready: the ETH and network is not up and running yet.
So, it has to fail.

So, the definition of a static variable with an assignment of a function call result - is "unpredictable"! Even no compile error, potentially no crash - the call of this function is potentially "too early" (system is not yet ready, not all initialized yet ... and nothing to do to tell compiler "just do it after I did this").

For me: I try to avoid to assign the result of a function call to static variables. I do not want to have code execution during startup, before my main() and setup() was really done.

Thanks for posting this. A read head scratcher of an error. And just imagine how much harder it would have been to track down if one had static in there from the beginning.

If this is really the source of the problem, you may have found a bug in the compiler. Or do I perhaps misinterpret the following text from the C++ standard?

The following example seems to support this.

void setup() {
  Serial.begin(9600);
  delay(1000);
}

void loop() {
  static unsigned long myVar = millis();
  Serial.println(myVar);  // Prints `999`.
}

Note that the value is set from the function call only the first time loop is executed, hopefully you are not expecting the value to be updated at the beginning of each loop.

That was not the point of the example, but yes you are correct.

My comment was intended for the OP, but I did not notice the original post was from June, so it is unlikely they are still around.

1 Like

Just more details...
static variables behave (are actually) like global variables: they are initialized during startup, when this "startup.s" code is done. So, they are initialized via a copy from FLASH ROM to RAM (or a loop to write RAM). This is done way before the main() function is called.
See in your startup.s code how global (and static) variables are initialized (writing/copying to the .data and .bss sections, see also linker script).

So, the initialization of a static inside a function is NOT done on the very first call: instead: the static variable sits outside the function (like any other global variable) and is initialized during startup. So, on the very first call of this function, the value for this variable is already written in memory by startup code.

Using C++, such initialization can be also the result of a function call. Constructors of global instances (objects) of a class as well as such static initialization via function call is done way before main() is called (see .ctors and .dtors in linker script).

So, what can happen is this issue:
the static var = function_call(); is done way before main(). But if main() needs to be executed which will initialize the stuff so that function_call() can work (e.g. initialize a driver) - it is too early for this initialization.

Take this example code:

void myFunc(void) {
    static i = UseMyDriver();
    //this function is called BEFORE main() was called!
}

void main(void) {
   InitMyDriver();
   //this initializes my driver and JUST AFTERWARDS any other functions on it
   //can be called, e.g. UseMyDriver() needs this been done FIRST!
   //...
}

The compiler uses the static just to "limit" the visibility for such variables in other modules, other functions (restrict it from seen as a global variable) but technically these are like global variables (initialized on startup, way before main and any other function called).

BTW:
The same "uncertainty" (risk) can exist with 'correct' C++ code:
Imagine, you would deal with global objects and the use of those for static:

class MyClass {
   MyClass();    //the constructor
   int UseIt();          //a regular method
};

MyClass myObj = MyClass();    //create a global object

void MyFunc(void) {
    static i = myObj.UseIt();
}

It is "unpredictable" (for my feeling or constraints) when are the constructor MyClass() and the method UseIt() are called: the order is IMPORTANT here, but is it really guaranteed when is which function called?
The compiler has to be smart to know, first Constructor, afterwards anything else can be called and be used.
Potentially, the C++ compiler will handle it properly. If not, no compile error bnecause myObj is global and potentially initialized via startup as all zero (like a default NULL pointer). So, myObj would exist already but not clear if this initialization via function call is done in the right order.

But if your uses C-code style, functions which need a specific order when to call (and able to call it) - you are in charge to understand when is which code executed.
And this is the problem here: a function is called too early at the end and it can fail because the prerequisite function was not yet called.

NO!
They are initialized the first time program execution passes through the statement where they are defined.

I stopped reading the rest of your post after that. No point given that your initial premise and understanding is incorrect.

I’m sorry but you talk utter rubbish sketch.ino - Wokwi ESP32, STM32, Arduino Simulator

This contradicts the quote from the C++ standard in post #3, so if you really observe this behaviour, you may have found a bug in the compiler. I can not reproduce this (also see the example in post #3).

Same

Does it make any difference when and where they are initialised ? Surely what matters is that they are initialised with any value given, that their value can only be changed within their scope and that they retain any value given to them even after their scope is exited

Sure it does. Let's say your setup() function takes a long time .... connecting to WiFi or something. But, you do micro()-based timing in your loop() function:

void loop() {
  static unsigned long lastMicros = micros();
.
.
.
}

This initializes lastMicros the first time loop() is called, not at some ill-defined "startup" as @tjaekel claimed.

The chances of finding a bug in the compiler are very slim, not worth even considering until all other options are exhausted, for example OP misunderstanding of static usage is way more probable.

Unfortunately not as slim as I would like, I happened to run into a fresh one just the other day (and reported it).

I do not know the exact conditions under which @tjaekel has found a problem, I would be interested to see a minimal example to analse.

Which compiler, can I see the report?

Sure. It was g++, the report is here.

[edit]

Whoops, wrong link. I corrected it.

Fair enough, looks like 9 year old bug that hasn’t been fixed.

I "love" people telling me: "you are wrong", without to provide help what is wrong or why I am wrong. :sweat_smile:

See example code below (as plain C-code) and what it would tell you:

  1. static variables are like global variables and they are initialized during "compile time" - not during "run time"
  2. there is not a concept (or hidden code generated) in order to distinguish between the 1st and a 2nd call of a function (the static variable is already initialized before the 1st call, done by startup code)
  3. you can (potentially) touch (overwrite) a static variable (even "hidden" in source code): if you would know its address, or you overwrite your memory where the global variables are located - you overwrite also static variables

Try this example code:

int GVar = 1;
int *sAddr = (int *)4;			//initialized: .data; not initialized (implicit 0 or as 0): .bss

void FuncWithStatic(void)
{
	static int lStaticVar = 2;	//compiler optimizes to do on startup code! - NOT on first call!
	sAddr = &lStaticVar;            //but this must be executed by calling the function

	lStaticVar = 3;

	print_log(UART_OUT, "    FuncWithStatic() was called\r\n");
}

void test(void)
{
    printf("&GVar=0x%08lx | &sAddr=0x%08lx | sAddr=0x%08lx\r\n", (uint32_t)&GVar, (uint32_t)&sAddr, (uint32_t)sAddr);

#if 1
    //run first time w/o this code!
    //for 2nd 'compile & run' use the address spit out
    printf("--> *sAddr=0x%08x\r\n", *((int *)0x24000014));	//figure out the address of lStaticVar
#endif
    FuncWithStatic();
    //watch the address spit out and modify above
    printf("    sAddr=0x%08lx\r\n", (uint32_t)sAddr);
    printf("--> *sAddr=0x%08x\r\n", *sAddr);
}

It generates this output:

&GVar=0x24000010 | &sAddr=0x24000018 | sAddr=0x00000004
--> *sAddr=0x00000002
    FuncWithStatic() was called
    sAddr=0x24000014
--> *sAddr=0x00000003

So, you can clearly see:

  1. the static lStaticVar is already initialized before you call a 1st time the function (sure)
  2. the address in memory of this lStaticVar is right in between two other global variables
  3. and if you would "figure out" the address of lStaticVar - you can access via memory pointer
    (write and read "hidden" static variables)

So, what is wrong with me? :sweat_smile:

Just one "trick" when you try this code:
compile and run a 1st time - get the memory address for the lStaticVar,
and a 2nd compile and run where the "figured out" address is used

There is NOT a bug in compiler! It is just: your compiler has no clue about your code "sematic" (the logic in your code).

Here a failing C++ example:

  1. assume, the main initialization is done in method "InitFunc()", e.g. initialize the UART
  2. but you call "UseFunc()" before it was done - logically WRONG (and you use UART before it was initialized) - the compiler cannot see "your" wrong 'call hierarchy' (and bug in "run-time logic")

And here a very drastically failing example:
The difference is "just": myObj is now a pointer to my instance, but look at the COMPILE CLEAN code you get and how it will fail during runtime:

//trace counter
int c;

class MyClass {
public:
	MyClass()
	{
		//this CANNOT work!: UART is not initialized!
		//print_log(UART_OUT, "MyClass object created\r\n");
		var = 1;
		c = 1;
	}

	int UseFunc(void)
	{
		c++;
		//even here we CANNOT use yet UART! not yet initialized
		return var;
	}

	void InitFunc(void)
	{
		c++;
		//this is actually needed to initialize, before we call UseFunc()
                //we do the UART initialization just now and here!
		var = 2;
	}

private:
	int var;
};

MyClass *GObj;

void testOOP()
{
	//we use before we do our actual configuration, via InitFunc() needed to call first!
	static int lStaticVar = GObj->UseFunc();

	print_log(UART_OUT, "c=%d | lStaticVar = %x\r\n", c, lStaticVar);
	print_log(UART_OUT, "call InitFunc()\r\n");
	GObj = new MyClass;
	GObj->InitFunc();

	lStaticVar = GObj->UseFunc();
	print_log(UART_OUT, "c=%d | lStaticVar = %x\r\n", c, lStaticVar);
}

Code is fine, compile clean, but: GObj is used before initialized!
The GObj does exist already (as a variable, but not with valid pointer assigned), we can do GObj->UseFunc(), but it gives us wrong result!

UART output:

c=1 | lStaticVar = 4ff0e92d
call InitFunc()
c=3 | lStaticVar = 2

See the garbage value for lStaticVar!

And if you change to not using a pointer, instead a global object: the "logic" is still wrong because you want to have "InitFunc()" called first before "UseFunc()" is called (your code logic)

==> the code logic matters! and you can have still wrong code, even all is compile clean

This was my topic, my message.

Why do think it was 'fair' to fire "you talk utter rubbish" against me?