PDA

View Full Version : static local variables aren't static



jimparis
03-19-2014, 01:12 AM
This sketch:


class Foo {
public:
Foo();
};
Foo::Foo() {
Serial.println("Foo()");
}
void setup(void) {
Serial.begin(115200);
delay(5000);
}
void loop(void) {
static Foo foo = Foo();
Serial.println("loop");
delay(1000);
}

doesn't work right on Teensy 3.1:


Foo()
loop
Foo()
loop
Foo()
loop

but works fine on Teensy 2.0:


Foo()
loop
loop
loop

This is with

arm-none-eabi-gcc (PJRC Build of GNU Toolchain from CodeSourcery) 4.7.2

W.O.
03-21-2014, 01:39 PM
static local variables are "normal" variables on Teensy 3.1

compile code with: Teensyduino 1.18 Arduino version 1.0.5. r2

void loop() {
delay(100);

unsigned long timeStamp = micros();
static unsigned long timeStampOld = timeStamp;
unsigned long timeStampDur = timeStamp - timeStampOld;
timeStampOld = timeStamp;

Serial.println(timeStampDur);
}

Output:
0
0
0
...

Compile this code for an Arduino Mega 2560 with the same IDE:
The otput is ok:

0
100120
100376
100380
...

I request a FIX - THANK YOU

nox771
03-21-2014, 05:26 PM
The problem appears to be not static exactly, but the non-constant initializer here:




void loop() {
delay(100);

unsigned long timeStamp = micros();
static unsigned long timeStampOld = timeStamp;
unsigned long timeStampDur = timeStamp - timeStampOld;
timeStampOld = timeStamp;

Serial.println(timeStampDur);
}


If I use this code:

#include <Arduino.h>

void setup()
{
Serial.begin(115200);
delay(5000);
}
void loop()
{
static uint32_t a=0;
static uint32_t b;
uint32_t c=micros();
static uint32_t d=c;
a++;
b++;
d++;
Serial.print("a="); Serial.print(a); Serial.print("\n");
Serial.print("b="); Serial.print(b); Serial.print("\n");
Serial.print("c="); Serial.print(c); Serial.print("\n");
Serial.print("d="); Serial.print(d); Serial.print("\n");
delay(1000);
}


Then I get this output:

a=1
b=1
c=5004033
d=5004034
a=2
b=2
c=6004067
d=6004068
a=3
b=3
c=7004100
d=7004101

In the case of constant initializers the static variables work fine, but for the variable initializers they are getting reset.

stevech
03-21-2014, 05:31 PM
Seems like one cannot initialize a static local using a non-static local, as is done in the code shown:

static unsigned long timeStampOld = timeStamp;

I'd think that would cause a compilation error message since the value of timeStamp, above, is not known at compile time.

EDIT: after I posted, I see another similar comment.

W.O.
03-22-2014, 12:20 AM
I compile the code posted by nox771
with: Teensyduino 1.18, Arduino IDE 1.0.5. r2
Board: Arduino Mega 2560 rev3
Output:

a=1
b=1
c=5000064
d=5000065
a=2
b=2
c=6001220
d=5000066
a=3
b=3
c=7002372
d=5000067


Board: Arduino Nano ATmeg328
Output:

a=1
b=1
c=5000060
d=5000061
a=2
b=2
c=6001196
d=5000062
a=3
b=3
c=7002328
d=5000063

ALL initializers of the static variables work fine.

Question 1: Which behavior is correct? (Arduino Boards or Teensy 3.1)
Question 2: Which behavior have Teensy 2.0 and Teensy ++2.0 boards with this code? (I don't have it.)

THANK you for your answers.

nox771
03-22-2014, 02:03 AM
Question 1: Which behavior is correct? (Arduino Boards or Teensy 3.1)


I'm not enough of a C++ guru to know the definitive answer, but I believe the Arduino boards are correct.

As near as I can tell:

For C code, static local variables must have a constant initializer
For C++ code, static local variables can have a variable initializer


It looks like a compiler error, and if so I wouldn't hold my breath waiting for a fix. It might be something that could be corrected in the .ld file, or with a compiler switch, but I have no idea how to do that.

A workaround is to move such variables to be global, and then initialize them somehow in setup(). It's not clean, but then again you can at least move forward on your project.

W.O.
03-22-2014, 07:19 PM
A workaround is to move such variables to be global, and then initialize them somehow in setup(). It's not clean, but ...

It's not clean - Yes, I agree! (in a bigger project you have a lot of such global variables ...)

But my main problem with global variables is:
I put data from a GPS in static variables. This data are available after the GPS has found enough satellites, or not available if the GPS is covered.
In this time hang the code in setup() waiting for initialization.

Your information "constant initializers the static variables work fine" bring me to this
Workaround: (local static variables with variable initializers for Teensy 3.1)


#include <Arduino.h>
void setup()
{
Serial.begin(115200);
delay(5000);
}
void loop()
{
static uint32_t a=0;
static uint32_t b;
uint32_t c=micros();

// Workaround STATIC Start
//static uint32_t d=c;
static uint32_t d=0;
static bool I_RememberFirstRun = true;
if(I_RememberFirstRun){
I_RememberFirstRun = !I_RememberFirstRun;
d=c;
}
// Workaround STATIC End

a++;
b++;
d++;
Serial.print("a="); Serial.print(a); Serial.print("\n");
Serial.print("b="); Serial.print(b); Serial.print("\n");
Serial.print("c="); Serial.print(c); Serial.print("\n");
Serial.print("d="); Serial.print(d); Serial.print("\n");
delay(1000);
}

Output:


a=1
b=1
c=5004042
d=5004043
a=2
b=2
c=6004075
d=5004044
a=3
b=3
c=7004109
d=5004045


If somebody has a other workaround, please post it.

PaulStoffregen
03-22-2014, 10:44 PM
I'm looking into this now.

Static local variables do work when initialized with constant data (the most common usage). For example, this works:



void setup() {
}

void loop() {
static unsigned long count=0;
delay(500);
Serial.println(count++);
}


It's the non-const case that's broken. Apparently the compiler is supposed to store a bool (http://stackoverflow.com/questions/838917/how-does-the-compiler-resolve-infinite-reference-loops/838932#838932) to know if the variable was already initialized (only for the non-const case where it has to initialize the variable at runtime), but somehow that's not working.

I'm investigating now.....

PaulStoffregen
03-22-2014, 11:32 PM
Here's a fix. Copy this to hardware/teensy/cores/teensy3.

Please let me know how this works for you?

nox771
03-23-2014, 05:07 AM
Works for me on the simple test case above.

W.O.
03-23-2014, 01:20 PM
Please let me know how this works for you?

It works fine in my project.

Thank you very much

stevech
03-23-2014, 06:55 PM
I live and learn. I thought that static variables had to initialize using values known at compile-time.
A class constructor is a similar case though, I suppose.

jimparis
03-24-2014, 11:07 PM
Here's a fix. Copy this to hardware/teensy/cores/teensy3.

Please let me know how this works for you?

Yep, that does the trick. Thanks Paul.

christoph
04-23-2014, 12:29 AM
I've run into the static initialization order fiasco (http://www.parashift.com/c++-faq/static-init-order.html), meaning that e.g. static variable A used in constructor B is initialized after B's constructor is called. This clears all changes to A made by B's constructor. The behaviour is very hard to reproduce (if at all), and forces me to use globals and put them into a single compilation unit. The common workaround is to create singleton or monostate classes that initialize a static variable on first use, yet that approach doesn't seem to work reliably with teensyduino 1.18 as downloaded from the pjrc site.


Can the changes to mk20dx128.c as posted above in #9 (potentially) solve this problem?
Are those changes included in the download from the pjrc site (https://www.pjrc.com/teensy/td_download.html)?

I'm really not sure if I have the right library code here...

Regards

Christoph

PaulStoffregen
04-23-2014, 05:23 AM
This fix will be in version 1.19.

christoph
04-23-2014, 12:29 PM
Thanks Paul, but I'm not sure if the fix would solve my initialization problem, hence my first question. What's your gut feeling about this?

When will 1.19 come?

PaulStoffregen
04-23-2014, 12:48 PM
but I'm not sure if the fix would solve my initialization problem

Well, did you try the fix?

All you have to do is grab that file from reply #9 copy it to hardware/teensy/cores/teensy3, replacing the original copy.

If that doesn't fix whatever problem you're seeing, wouldn't it be good to know now, rather than waiting weeks for a 1.19 release?

christoph
04-23-2014, 02:34 PM
Well it seems to solve the problem. The different observed behavior can just as well be caused by the code simply being different now - this is really hard to track down.

I have a number of other globals that suffered from initialization order problems and got moved into a globals.cpp (well, not just globals but static class members as well). I'll put them back into their own compilation units one by one and see if they behave differently now, and then report again here.

Regards

Christoph

MichaelMeissner
04-23-2014, 04:14 PM
Perhaps using the Gnu C++ extension to specify the init priority would help. I don't know whether this is done in the linker, or whether it needs runtime support that might or might not be present in the Teensy/Arduino environment: http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes

wwg
04-23-2014, 04:41 PM
Perhaps using the Gnu C++ extension to specify the init priority would help. I don't know whether this is done in the linker, or whether it needs runtime support that might or might not be present in the Teensy/Arduino environment: http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes

The Ada compiler (gnat) determines the order of elaboration based upon dependencies (i.e. the sequence is entirely determined by the compiler). In fact gnat might be using this gnu extension to implement that at the linker level (but don't quote me). Someday I'd love to see Ada support for the teensy3 (See AVR-Ada: https://sourceforge.net/projects/avr-ada). With the fatter teensy3.x resources available, the teensy version wouldn't need to be stripped down as much as the AVR case, but I digress....

Unfortunately, in C++ (as you've said) there is no defined elaboration. It is probably best to avoid the need for this by planning your code so that the init sequence doesn't matter (or can be controlled).

christoph
04-23-2014, 08:25 PM
Unfortunately, in C++ (as you've said) there is no defined elaboration. It is probably best to avoid the need for this by planning your code so that the init sequence doesn't matter (or can be controlled).

Actually, there is a common workaround. Here's a class with a static member:

class hasPlainStaticMember
{
public:
static some_type staticMember;
};

some_type hasPlainStaticMember::staticMember;

We don't know when staticMember is initialized relative to other static variables in other compilation units. However, static variables in functions or methods are initialized on first use, so we can wrap each static member in an access method:

class hasWrappedStaticMember
{
public:
some_type& staticMember()
{
static some_type member;
return member;
}
};

Note that accessing requires an additional pair of parentheses, but we can now implement additional protection, such as returning a const reference.

I totally don't care about the order of initialization as long as variables are initialized when I want to "use" them for the first time. I have yet to encounter code where this is not the case, i.e. where the absolute order of initialization matters regardless of when a variable is first used.

christoph
04-24-2014, 09:00 PM
Paul, I just want to thank you for this fix. That was über useful! ;)

PaulStoffregen
04-25-2014, 11:27 AM
Glad it helped. I really need to get version 1.19 released soon... but with Maker Faire coming up, a new release might slip until early June.