Weird behavior with static functions and variables

In the code below, the sprintf statement is not putting what I expect for the second and third replacements.

class Sensor {
protected:
static void formatString(char* result, char* type, uint32_t value);
private:
static unsigned long my_id;
};
unsigned long Sensor::my_id = 1;  // i don't even know how the hell this works since it's private, but it does

void Sensor::formatString(char* result, char* type, uint32_t value) {
  sprintf(result, "blah long string %d some more %s and then some more %lu", my_id, type, value);
  // I tried changing my_id to Sensor::my_id in hopes that it would help, it didn't
}

I have an array of pointers to sub-classes of this class:

class Motion : public Sensor {
public:
void gimmeStuff(char* result) { Sensor::formatString(result, "motion", 42); }
}
class Light : public Sensor {
void gimmeStuff(char* result) { Sensor::formatString(result, "light", 42); }
}
Sensor* arr[2];
arr[0] = new Motion;
arr[1] = new Light;

I call it from the loop function like so:

void loop() {
  for(int i = 0; i < 2; i++) {
    char bob[100];
    arr[i]->gimme_stuff(bob);
    Serial.println(bob);
  }
}

I end up with:

"blah long string 1 some more  and then some more <crazy number that doesn't mean anything>"

If I put this in a loop, the <crazy number that doesn't mean anything> is the same for each

If I put in Serial.println statements for the two variables above the sprintf, the print out the correct values (so they are being passed in correctly).

It appears as though the fact that one of the variables passed as an argument to sprintf is static, and it is therefore choosing to believe the others ones are as well and pulling random values for them out of nowhere.

The standard g++ compiler on my mac does not appear to have this same issue.

Can you post a complete piece of code that demonstrates this? Not just snippets?

unsigned long Sensor::my_id = 1;  // i don't even know how the hell this works since it's private, but it does

What does the fact that the field is private have to do with anything? What file is that snippet in? If it's in the .cpp file that is named to match the .h file that contains the earlier snippet, that code has the right to set public, protected, or private fields. No mystery there.

Why is the formatString() method static? For that matter, why is it a class method at all? It doesn't appear to be related to the class, and does not use any class methods or fields.

Full listing that produces this behavior:

class Sensor {
public:
  virtual void getReading(char* result);
protected:
  static void formatString(char* result, char* type, uint32_t value);
private:
  static unsigned long my_id;
};

void Sensor::getReading(char* result) { }

void Sensor::formatString(char* result, char* my_type, uint32_t my_value) {
  sprintf(result, "my_id: %d, type: %s, value: %d", my_id, my_type, my_value);
}

unsigned long Sensor::my_id = 1;

class Motion: public Sensor {
public:
  virtual void getReading(char* result);
};

void Motion::getReading(char* result) { 
  Sensor::formatString(result, "test", 20);
}

Sensor* my_item;
void setup() {
  my_item = new Motion;
  Serial.begin(9600);
}

void loop() {
  char my_string[150];
  my_item->getReading(my_string);
  Serial.println(my_string);
  delay(1000);
}

The method is part of the parent class because it is a method used to generate a specific string that is the same across all sensor types, varying only on type of sensor.

The method is static because I would like to be able to use it without instantiating the class if necessary.

The variable is static because there is never any reason, within a single run of the application, to need it defined on a per sensor basis (the ID is generated from the board, not the sensor). I haven't written the generator function yet, I am simply mocking it via setting the variable directly.

I'm unfamiliar enough with C++ to not be aware that the cpp file associated with a .h file magically has access to private variables of the classes defined in the .h file.

-Luke

Your format specifiers have to match the supplied types:

void Sensor::formatString(char* result, char* my_type, uint32_t my_value) {
  sprintf(result, "my_id: %d, type: %s, value: %d", my_id, my_type, my_value);
}

If you change the two %d to %ld (long decimal) it works fine.

void Sensor::formatString(char* result, char* my_type, uint32_t my_value) {
  sprintf(result, "my_id: %ld, type: %s, value: %ld", my_id, my_type, my_value);
}

Output:

my_id: 1, type: test, value: 20
my_id: 1, type: test, value: 20
my_id: 1, type: test, value: 20
my_id: 1, type: test, value: 20
my_id: 1, type: test, value: 20

Totally fixed it, thanks!

Correct me if I'm wrong, but probably what was happening was:
When referring to the static variable, it also used static memory space for the parameters.
I was only passing a 1 byte integer in.
The parameters were being initialized as a 4 byte integer.
I was only reading 2 bytes of that integer.
The 2 bytes I was reading in the sprintf were not the 2 bytes that had the actual useful information in them.

Either way, thanks so much for your help.

Yes, basically. If you have the format specifier wrong it will skip over the wrong number of bytes and get garbage. Under some circumstances that garbage may be zero so it "looks right".