The HATRED for String objects - "To String, or not to String"

I going to agree with PaulS here. In your (rather obscure) example pYro_65 you set up a case where the compiler chose a global instead of a class variable by using the same name for both. To fix the sketch BTW you need to change the derived class to:

template <typename T> struct Squared : 
public Base<T>{
  Squared( int i_NewVal ) : 
  Base<T>( i_NewVal * i_NewVal ) {
    return;
  }
  T Result() { 
    return Base<T>::i; 
  }
};

Notice the qualification of "i" there. But you could say the same thing about virtually any variable. If you had a global i_NewVal and then misspelt i_NewVal in the constructor you would have a similar issue.

A fairly simple fix in fact is to have a naming convention for class variables, eg.

int m_myVariable;   // m_ = member; or:
int myVariable_;    // using trailing underscore on class variables

Yes I prefer namespaces or trailing '_G'

The example is a good point I think as many are avoiding 'this->' to keep their code 'pretty', whereas blatant use will save you without even knowing it was a problem. i_NewVal is fine as its callee scope is non-dependant. Further templated functions would require further qualification.

The important part of my post was showing how the global was the sole cause of problems, the code otherwise would not have compiled.

So rather than receiving an error about 'i' being undefined in the class ( 'this->' is required to make the name dependant ), the previous definition of 'i' is silently used.

Interesting and thank for clarifying this.

One question tho

When allocation static memory does that automatically become a global variable ?

Kim

I'm not sure I understand your last question... Are you asking if qualifying a variable as "static" makes it global ?

Scope and lifetime are two related but distinct properties of a variable.

fiddler:
Interesting and thank for clarifying this.

One question tho

When allocation static memory does that automatically become a global variable ?

Kim

No. A global (i.e., defined in the "global" scope) variable and static variable are different.

A global can be accessed anywhere. A static can only be accessed in the function it is defined in. A static variable retains its value across subsequent calls to the function.

The memory for a static is, as the name suggests, static - it is always at the same location, so theoretically it can be accessed globally through the address:

int *superGlobal;

void myFunc()
{
  static int localStatic = 4;
  superGlobal = &localStatic; // Assign address of localStatic to superGlobal pointer
  localStatic++;
}

void myOtherFunc()
{
  int myLocal;
  myFunc();
  myLocal = *superGlobal; // "myLocal" now contains whatever localStatic contains.
}

Not something that is done often - it's more common to either return the value from the function, or return a pointer to the static variable. Such functions, though, are not re-entrant, and are frowned on in today's multithreaded environments. Perfectly fine on the Arduino though :slight_smile:

:fearful:

I hope you are aware that the code above is fine as an "extreme" didactic example, but it's definitely a coding horror(1). And the reason is, it's the perfect recipe for spaghetti code :wink:

(1) As in "Code Complete 2"; see Jeff Atwood's blog icon for an effective visual representation of the concept :slight_smile:

Re-entrant code can build up the stack really fast if you're not very, very careful. Remember, UNO has only 2k of RAM for stack and heap space.

Thanks

Coming from Assembly and a bit of basic, I'm trying to get my head around the GNU C compiler and the way it does things.
Been reading the GNU documentation, but sometimes it is "clear as mud" :slight_smile:

K

GoForSmoke:
Re-entrant code can build up the stack really fast if you're not very, very careful.

You're thinking of recursive code?

Right. Functions that call themselves or call others that call them, etc.

It's been a while since I wrote that kind of stuff.

GoForSmoke:
Right. Functions that call themselves or call others that call them, etc.

It's been a while since I wrote that kind of stuff.

Re-entrant is entirely different.

Re-entrant code can be safely called by two different threads at the same time without trashing any global / static variables. They often involve passing of pointers to work areas to the function, so each thread has its own bit of memory.

Something else I wouldn't try with a 328P chip.

Long live the state machine!

GoForSmoke:
Something else I wouldn't try with a 328P chip.

Long live the state machine!

Hell yeah :slight_smile:

I had fun a bit back trying to move a few non-re-entrant library routines (from the printf family) into kernel space on RetroBSD so as to reduce user-process size (kind of a pre-cursor to shared libraries) a bit back... Not nice trying to convert them to re-entrant :wink:

The String class is kind of like training wheels on a bicycle. Babies need them in order to learn how to ride (or code), but as soon as you want to do anything remotely useful you have to take them off and throw them away.

So many of the "Why don't they fix this bug?" complaints could easily be rewritten as:

"Wah! I want to be able to do BMX tricks but I'm scared to let go of my training wheels."

Delta_G:
The String class is kind of like training wheels on a bicycle. Babies need them in order to learn how to ride (or code), but as soon as you want to do anything remotely useful you have to take them off and throw them away.

So many of the "Why don't they fix this bug?" complaints could easily be rewritten as:

"Wah! I want to be able to do BMX tricks but I'm scared to let go of my training wheels."

Wow, another String class whiner! :roll_eyes: Sad that the String class whiners almost never correct/adjust the usually simple they constantly whine about. Sad

Re-entrant would also be where an ISR calls a function.

Say you are in the middle of a Serial.print. A timer interrupt fires. Inside the timer you do a Serial.print (a bad idea, I know). Then the Serial.print function is "re entered". And say inside Serial.print it keeps a variable (eg. for working out how to convert a float). That variable is now changed by the re-entry. So the original one (the one that got interrupted) is now corrupted.

This is another reason why you should keep interrupt service routines short. And why those people that try to work around Serial.print "not working" inside an ISR by adding a line to turn interrupts on, are really shooting themselves in the foot.

Regarding the String class, there is actually a method 'reserve()' that was added with 1.0.

Strange enough totally undocumented on String() - Arduino Reference, but available: Google Code Archive - Long-term storage for Google Code Project Hosting.

I assume with this many dynamic mem allocation probs should be solved if one stays within the reserved memory. And we still can use the richer api. Tested it and it solved my mem leak problem immediately.

Is it love of C++ String, dislike of C string or just not used to C string that drives people to accept the behind your back actions and overhead RAM bytes of C++ String?
Look at all String does to "make it easier" to manipulate text in a number-like manner. Ooooh! Power windows! Hey, turn the car on just so I can open the window! No, that's not inefficient, that's con-veeen-yence!

GoForSmoke:
Is it love of C++ String, dislike of C string or just not used to C string that drives people to accept the behind your back actions and overhead RAM bytes of C++ String?
Look at all String does to "make it easier" to manipulate text in a number-like manner. Ooooh! Power windows! Hey, turn the car on just so I can open the window! No, that's not inefficient, that's con-veeen-yence!

How is your world of horses, buggies, and telegrams/bag phones? C strings are difficult for non programmers like me and appear to somewhat primitive operations compared to the ease of Strings. The "old timers" just don't get it. :wink: