Arduino Forum

Using Arduino => Programming Questions => Topic started by: cjdelphi on Sep 25, 2012, 03:56 pm

Title: The HATRED for String objects - "To String, or not to String"
Post by: cjdelphi on Sep 25, 2012, 03:56 pm
I've seen it so often now if as a whole the community is anti string to the extent it annoys members here...


Why not remove it (as in disable it as an option or something along the lines, default off you have to explicitly enable it somehow) ? obviously nobody likes it
instead replace it with a few handy char routines instead?
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: PaulS on Sep 25, 2012, 04:12 pm
Quote
Why not remove it

Mostly because the problems are not all with the String class. Its design is fine. It is the implementation that leaves a bit to be desired. The real problem, though, is that the free() function has a fundamental flaw that needs to be addressed.

Once that is, the String class will be marginal. It will be fine for small strings - a dozen characters read from the serial port, for instance.

Quote
instead replace it with a few handy char routines instead?

Like strcat(), strcpy(), strtok(), strcmp(), you mean? These already exist, and are well documented, and can be used instead, today. No change needed.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 25, 2012, 05:00 pm
You can get away with using the String Class with some care taken.. usually.

Problem is that many people don't know there's a reason to take care until it bites them, and then they post about a problem with crashes. You don't see many posts from people who managed to stay within limits, like the 2K RAM limit.

We will see a lot of this forever. People who learn C++ on PC's generally don't learn C strings and have no clue what goes on in the magic box let alone why a smaller box should be different.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: AWOL on Sep 25, 2012, 07:39 pm
Quote
I've seen it so often now if as a whole the community is anti string

No, not anti-string at all.

Very, very anti-String.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: Marvinfly on Sep 25, 2012, 07:55 pm
Quote
Problem is that many people don't know there's a reason to take care until it bites them, and then they post about a problem with crashes. You don't see many posts from people who managed to stay within limits, like the 2K RAM limit.


This is what I have found. I've been trying to 'teach myself' how to code, I have no background in Electronics or coding. I see something like a string that seems to make sense if used in the way I want to use it. Two weeks later... no, start again. It breaks and I had no way of knowing why, or what to use instead.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 25, 2012, 10:17 pm
The big caveat with C strings is making sure you don't write past the end of the array. You -must- code for that yourself either explicitly or implicitly (like never writing any string too long, not good for general use but okay for special cases).

Set yourself up with some bookmarks, it's good to have references.

These are the library modules used in the AVR C++ Arduino uses:
http://www.nongnu.org/avr-libc/user-manual/modules.html

This is the C string library page:
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html

You #include <string.h> to use those functions. All the names are shorthand, you get to know them.

Some easy basics you can do most simple things with:
strlen() is string length
strcpy() is string copy, it puts the terminating zero at the end of the copy. It is string = string.
strstr() is string string, it searches for a substring within a string
strcmp() string compare tells you if one string is >, ==, or less than another, good for sorting
strcat() string concatenation, adds one string to the end of another

You will some with an n in the middle. The n tells you that character count is used.

strncpy() is strcpy for up to n characters and does NOT put a zero at the end.
strncpy is perfect for writing over part of a string with another, in BASIC it is mid$()

Not simple but very useful is strtok(), string token, that you can use to parse strings with.

Also don't forget the mem (memory) functions, the 2 most basic:
memset(), to set some number of bytes equal to a given value
memmove(), copies bytes and **is safe to use when the destination overlaps the source**

There's more of all of them. And if you don't see what you want then remember that C strings are just 1 dimension byte arrays you can easily process in loops without needing any library whatsoever. Those functions are only for convenience once you understand how C strings work.

One function C strings don't have that C++ String Class does is a function to tell you where the data actually is. That's because with C strings you don't one, ^^ YOU tell the function where the data is and where it goes ^^ and IT doesn't go anywhere else, unlike mind-of-their-own String objects.

Hope this helps with your jitters. The territory is really quite simple and rock solid stable.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: Ian_Lang on Sep 25, 2012, 11:25 pm
Me, I love strings. Use them all the time and never have a problem with them. I hate chars.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 26, 2012, 12:40 am

Why not remove it (as in disable it as an option or something along the lines, default off you have to explicitly enable it somehow) ?


Instead of removing strings, they just have to make about a 1-line change to the library code in free() that has a bug in it. Then some, at least, of the problems will go away.

You potentially still have problems with memory fragmentation, but as some posters have observed, these do not always bite you.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: PeterH on Sep 26, 2012, 02:28 am
If stability is important then I don't think it is a good idea to use dynamic memory allocation on systems without a robust memory management system, unless you understand the patterns of allocation and deallocation that will occur. I doubt that it would ever be practical to implement a generally robust memory management system within the constraints of an Arduino.

At the same time, dynamic helper classes such as String are IMO going to be particularly useful to novices because they take away the pain of dealing with buffers and points and so on. So the people most likely to use this are also the ones least likely to understand when and how to use dynamic allocation safely.

Given that one of the main goals of Arduino seems to be to make development accessible to novices, this strikes me as a step in the wrong direction.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: MarkT on Sep 26, 2012, 02:50 am

Quote
Problem is that many people don't know there's a reason to take care until it bites them, and then they post about a problem with crashes. You don't see many posts from people who managed to stay within limits, like the 2K RAM limit.


This is what I have found. I've been trying to 'teach myself' how to code, I have no background in Electronics or coding. I see something like a string that seems to make sense if used in the way I want to use it. Two weeks later... no, start again. It breaks and I had no way of knowing why, or what to use instead.


The problem with the String class on the Arduino is that it assumes sophisticated memory management (like a reference-counting or other garbage-collector).  You don't have that on a 2K microcontroller, so eventually intermediate results (usually of string-concatenation) bung up memory and the processor crashes.   Compilers cannot analyse every possible execution of your code so they cannot determine every case of where a String object becomes unreferenced and thus can be freed.

Using char * rather than String forces the programmer to handle string lifecycles and memory allocation, and the programmer usually knows how the program is mean to run and when a char array can be reused.

Compare with the String class in the Java language - this does have a garbage-collector to recover dead objects so you can have nice intuitive String operations and memory runs out only if you genuinely are hanging on to too much stuff - you never have to call free() and it all just works.  But that runs on systems with MB of RAM and ROM...
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 26, 2012, 12:06 pm
What Nick said, a small fix to free() would do wonders for use of the String class.

Just plain 'strings' aren't a problem at all. It's the Strings that trip so many people up.

Quote
Using char * rather than String forces the programmer to handle string lifecycles and memory allocation, and the programmer usually knows how the program is mean to run and when a char array can be reused.


It forces an MCU programmer to know about the hardware which is blasphemy to purist comp-sci dweebs. How many programmers does it take to change a light bulb? Can't be done, it's a hardware problem!

Title: Re: The HATED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 26, 2012, 11:43 pm
I'm going to agree with GoForSmoke here. Tests in other threads have shown that, with free() fixed, code that previously crashed regularly worked indefinitely.

Whilst is it possible to fragment and run out of memory with only 2 Kb to spare, code that allocates (and then frees) the same sizes strings all the time will not suffer from it.

In particular, if you avoid doing string concatentation which is probably the worst offender in causing fragmentation. (That is, building up a string by adding one character to it all the time). In any case, this sort of string-building is necessarily slow.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: kf2qd on Sep 27, 2012, 12:11 am
The main problem with Strings is, with a limited RAM machine the potential to have a problem because you didn't properly account for how (worst case) memory might be used, and how you will deal with the condition that results. On small, simple programs you won't see much problem, but as the programs grow the need for total memory management BY THE PROGRAMMER becomes more and more the major requirement. One call to a function that uses Strings without having memory properly cleaned up and things crash. Is that the fault of Strings, or bad programming practice by a programmer that didn't keep track of his free memory.

It is interesting to note that in the early days all programmers had memory usage tables and when they wrote a program they documented exactly how memory would be used because it was tight. Once again we are using a system with tight memory constraints and we expect the software to do the programmers job. If you want unlimited memory, don't use an arduino.

Oh - it wasn't all that many years ago that there existed machine with 4Kx8bit memory, for program AND data and we made it work... 16K x 8 bit cost $80.00 US.
Title: Re: The HATED for String objects - "To String, or not to String"
Post by: retrolefty on Sep 27, 2012, 01:08 am
Quote
Oh - it wasn't all that many years ago that there existed machine with 4Kx8bit memory, for program AND data and we made it work... 16K x 8 bit cost $80.00 US.


And before that in the minicomputer era a 16K x 16 bit core memory board would set you back some thousands of dollars.

Lefty
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: cjdelphi on Sep 27, 2012, 04:33 am
For example
void loop()
{
  char c[10];
  int n;


}

//what happens exactly here

with each iteration of the loop does a local variable get created (c/n) and recreated over and over again?... at the time i did not know so I simply made a global var c/n (but whatever i called them in my project)
so i knew i was never going to fill the memory full of undestroyed pointers and unused memory allocations (just to be on the safe side)

so with each loop would all the local variables be destoyed along with any memory allocation?
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 27, 2012, 04:47 am
In your example the stack is incremented* by 12 (10 plus 2 for the int) to make room for those variables. They are thus uninitialized because they have whatever was on the stack. When loop exits that stack space is reclaimed.

If you make them global the stack doesn't get altered but you have 12 bytes less available for the stack because they have to go somewhere.

The stack and heap share the same piece of memory (RAM) starting at wherever your global variables end. The heap grows upwards and the stack grows downwards from the top of memory. If they happen to collide: trouble!

* decremented really, because the stack grows downwards.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 27, 2012, 12:33 pm
I don't know if there's any time savings but a global gets allocated once while or making non-static locals or passing a load of parameters to a function happens over and over.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 27, 2012, 12:42 pm
True, a stack-based variable will take a couple of machine cycles to reserve the extra stack. If you only ever need the one copy, a global (or static) variable would suit your needs.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: majenko on Sep 27, 2012, 01:01 pm
Ok, guys.  For your reference, let me clear up how memory works ;)  (for those who know already, this is for those who don't.)

There are basically two types of "dynamic" memory:  the "stack", and the "heap".

The stack can be thought of like a pile of coins.  Coins are put on to the pile in order, and then removed afterwards in reverse order.

Any "local" variables, along with any variables passed to functions, are placed on the stack, used within the function they are defined in, and then removed from the stack again.

The stack starts at the top of the memory space, and grows downwards.

Then there is the "heap".  This is more like a mound of coins.  Coins can be put into the mound wherever they will fit.  If there is no room inside the mound for them, then they are dropped on the top of the mound.  Coins can be pulled out from anywhere in the mound.  The same goes for variables.  These are all "dynamically allocated" variables.  Anything that uses "malloc()" or "free()", and any classes or functions which use these functions within themselves.

The heap is located at the bottom of the memory area.

The two main issues with the heap are memory leaks and memory fragmentation.

Memory leaks occur when some function places coins into the mound, and then forgets that it put them there, so they never get taken out again.  Variables created in the heap must be removed after use, or the heap will just grow and grow and grow.

Memory fragmentation occurs when small variables are removed from the heap to be replaced by larger variables.  The space left by the small variable is too small to accept the larger variable, so the larger variable is instead placed on the top of the heap, causing the heap to grow.  This most often occurs when working with strings and you want to add something to the end of a string (concatenate) or join two strings together.

As the heap gets more and more fragmented and grows bigger and bigger over time, and as more and more local variables get pushed into the stack, there is a big risk that the heap and the stack will both grow so big that they meet in the middle.  When this happens you have problems.  Big problems.  This is when crashes occur.

You can get "memory de-fragmentation" programs for the PC which effectively re-arrange the heap to remove the holes in it.  This makes programs run faster, as they can allocate bigger chunks of memory, and helps to reduce the overall size of the heap.  Quite useful.  There is nothing (as far as I have seen) like it for micro-controllers - mainly due to the lack of space in the first place ;)

So, the issue with the String class isn't with the String class itself - it's a very useful class (in the right situations).  The issue is with dynamic allocated memory causing fragmentation in the heap, which overflows and crashes into the stack.

So yes, a fix to "free()" will improve things, but on a small micro-controller with very limited RAM space dynamic memory allocation in itself is something to be shunned whenever possible.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PaulS on Sep 27, 2012, 01:52 pm
Quote
So, the issue with the String class isn't with the String class itself - it's a very useful class (in the right situations).  The issue is with dynamic allocated memory causing fragmentation in the heap, which overflows and crashes into the stack.

Part of the problem IS with the String class itself. When you want to append one character to an existing String, the length of the new String is the length of the old String plus the length of the String to append. That amount of space is allocated, and the old String is copied there, then the String to be appended is tacked on, then the old String's space is freed.

Append another character, and the whole process is repeated, because the String has no room to grow.

On other platforms, extra space is allocated, so that there is some room to grow. Perhaps 10 extra bytes, so you can add 10 characters, before a malloc/copy/free operation is required again.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: majenko on Sep 27, 2012, 01:57 pm

Quote
So, the issue with the String class isn't with the String class itself - it's a very useful class (in the right situations).  The issue is with dynamic allocated memory causing fragmentation in the heap, which overflows and crashes into the stack.

Part of the problem IS with the String class itself. When you want to append one character to an existing String, the length of the new String is the length of the old String plus the length of the String to append. That amount of space is allocated, and the old String is copied there, then the String to be appended is tacked on, then the old String's space is freed.

Append another character, and the whole process is repeated, because the String has no room to grow.

On other platforms, extra space is allocated, so that there is some room to grow. Perhaps 10 extra bytes, so you can add 10 characters, before a malloc/copy/free operation is required again.

Well, yes the String class could be written better, but even written better it's still not a good class in the context of the Arduino - even with extra space it is still using dynamic allocation, and thus is bad.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 27, 2012, 01:59 pm
PaulS is quite right, and I think the String library initially allowed for that. However with only 2 Kb of RAM to play with they decided to eliminate (possibly) 9 extra bytes which may or may not be used. This gives an advantage in some situations and a disadvantage in others.

http://en.wikipedia.org/wiki/There_ain%27t_no_such_thing_as_a_free_lunch
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PeterH on Sep 27, 2012, 02:04 pm

with each iteration of the loop does a local variable get created (c/n) and recreated over and over again?


Yes. Automatic local variables are allocated on the stack and thrown away when the stack is unwound at the end of the call. In general, the more tightly you restrict the scope of the data the less chance there is for dependencies and unintended interactions between different parts of your code. This is why use of global data is best avoided where possible, and use of local data and arguments is generally preferred.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PaulS on Sep 27, 2012, 02:06 pm
Quote
I think the String library initially allowed for that.

Paul Stoffgren's implementation did. The Arduino one removed that.

The option to allocate data in other-than-one-character larger increments needs to be part of the String class.

Typically, the user of the class has an idea of how much data is going to be stored in the String instance. Setting the minimum increment to some small value for small Strings and a larger value for larger Strings (say 5 and 15) would drastically reduce the amount of malloc/copy/free operations performed, which would have a big impact on the fragmentation issue. The default minimum increment should, of course, be 1.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 28, 2012, 10:40 am


with each iteration of the loop does a local variable get created (c/n) and recreated over and over again?


Yes. Automatic local variables are allocated on the stack and thrown away when the stack is unwound at the end of the call. In general, the more tightly you restrict the scope of the data the less chance there is for dependencies and unintended interactions between different parts of your code. This is why use of global data is best avoided where possible, and use of local data and arguments is generally preferred.


With 2k RAM I prefer globals and keeping control of my code. A few locals don't hurt but whole buffers in short often-called functions, it's good to be aware of because some day you might be writing code that needs to be fast rather than 'do it this way every time'.

awareness != mandatory practice

I've likened using C++ Strings on Arduino because C++ Strings are okay on a PC to putting a bathtub on a bicycle just because your house has one. I still feel the same. There's a lot of coding practices I've happily done on PC's that I wouldn't think of doing on Arduino.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: retrolefty on Sep 28, 2012, 03:24 pm



with each iteration of the loop does a local variable get created (c/n) and recreated over and over again?


Yes. Automatic local variables are allocated on the stack and thrown away when the stack is unwound at the end of the call. In general, the more tightly you restrict the scope of the data the less chance there is for dependencies and unintended interactions between different parts of your code. This is why use of global data is best avoided where possible, and use of local data and arguments is generally preferred.


With 2k RAM I prefer globals and keeping control of my code. A few locals don't hurt but whole buffers in short often-called functions, it's good to be aware of because some day you might be writing code that needs to be fast rather than 'do it this way every time'.

awareness != mandatory practice

I've likened using C++ Strings on Arduino because C++ Strings are okay on a PC to putting a bathtub on a bicycle just because your house has one. I still feel the same. There's a lot of coding practices I've happily done on PC's that I wouldn't think of doing on Arduino.




I too like the use of global variables in my arduino projects. I like that I can define them all at the top of the sketch with comments on what they represent. I kind of limit my use of local variables to things like the for statements and such. I know this is against the concept of good variable scoping practice that C/C++ encourages programmers to use and does make sense for larger projects where teams of programmers are working on the same application program and have to not step on each others coding functions, but with the limited SRAM space and me being the only one writing the code, global variables work better for me and I haven't heard of a good reason I should avoid them in my Arduino sketches. Sometime I just have to be an outlaw in heart I guess.  ;)

Lefty
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Sep 28, 2012, 07:22 pm
Quote
Instead of removing strings, they just have to make about a 1-line change to the library code in free() that has a bug in it. Then some, at least, of the problems will go away.


So what is this one line change? Can it be posted so I can fix my library? I'd just rather fix things instead of hanger flying on the subject. Might eliminate some peoples constant string bashing and often mis diagnosis of problems people are having.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: gardner on Sep 28, 2012, 11:13 pm
The problem is that the fix is not to source that is built during normal sketch compilation, but to the libraries provided pre-compiled in Arduino.

Have a look at this bug http://code.google.com/p/arduino/issues/detail?id=857
and this thread: http://arduino.cc/forum/index.php/topic,95914.30.html

Personally, I am not a fan of String, even if some of the problems people run into are due to this bug.  Anything that smells of dynamic allocation has no place, in my opinion, on such a memory-constrained platform.  The problem is that statically allocated and strings and all the string.h stuff is complicated to a beginner.  The String *seems* easy, so you can get something going faster than if you have to grok pointers and in-memory layout of strings and crap just to print hello world.  But I think folks do not appreciate the limits of what you can do with String -- and with lots of other things like arrays and C strings too -- with so little memory, and wind up being forced to learn the details anyhow.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: foxbat on Sep 28, 2012, 11:57 pm

Part of the problem IS with the String class itself. When you want to append one character to an existing String, the length of the new String is the length of the old String plus the length of the String to append. That amount of space is allocated, and the old String is copied there, then the String to be appended is tacked on, then the old String's space is freed.

No this is not true. String uses realloc() which, if you'd care to read the implementation in realloc.c (http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/libc/stdlib/realloc.c?revision=2127&root=avr-libc&view=markup) will always attempt an in-situ block-expansion. For most cases this will result in no pointer change at all and a simple adjustment of the allocated length - usually out into free space in most use-cases on a small system like this. A block-copy is only required if the original allocation either came from the free list and that space is now exhausted or there is a new block allocated in front of the one that needs to be expanded.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: foxbat on Sep 29, 2012, 12:05 am

Memory fragmentation occurs when small variables are removed from the heap to be replaced by larger variables.  The space left by the small variable is too small to accept the larger variable, so the larger variable is instead placed on the top of the heap, causing the heap to grow.  This most often occurs when working with strings and you want to add something to the end of a string (concatenate) or join two strings together.
to be shunned whenever possible.


It's actually quite hard to cause significant memory fragmentation to occur in the avr-libc implementation of the heap. free() (http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/libc/stdlib/malloc.c?root=avr-libc&view=markup) will always coalesce blocks above and below the one it's being asked to release to create one larger block in the free list so even a poor String concat implementation that used malloc()+free() instead of realloc() would generally only end up with one block in the free list due to every second free() being coalesced with the previous one and then being big enough to satisfy the next malloc().

If you understand it, use it. If you don't, don't.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 29, 2012, 04:32 pm

For most cases this will result in no pointer change at all and a simple adjustment of the allocated length - usually out into free space in most use-cases on a small system like this.


Assuming the String is at the top of the heap in 'most cases'. Otherwise not.

Andy Brown, I think you will find that Nick knows more about the code than you credit. And he isn't given to make unfounded claims.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Sep 29, 2012, 06:01 pm
So is there anything in the external programming that can be done to free the no longer used allocated memory space?
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: retrolefty on Sep 29, 2012, 06:59 pm

So is there anything in the external programming that can be done to free the no longer used allocated memory space?


Perhaps enable the WDT, let if time out, and have the sketch start all over?  :D
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Sep 29, 2012, 10:42 pm
Quote
Perhaps enable the WDT, let if time out, and have the sketch start all over? 


Close to what my magic 8-Ball suggested.  :)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 29, 2012, 11:40 pm

Andy Brown, I think you will find that Nick knows more about the code than you credit. And he isn't given to make unfounded claims.


I don't have any argument with Andy. I think he has the fixed malloc/free library on his site anyway.

There is a bug in 'free' in the current Arduino library. I think we are all agreed on that. What Andy says about free spaces being combined by the library would almost certainly be correct. But the bug in question is something to do with the first (or last?) allocated block not being freed correctly, or something like that.

I have heard people say that, with the fixed free() installed, they can use String objects at some length with no problems, which supports what Andy says about the library combining unused blocks.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 29, 2012, 11:42 pm

So is there anything in the external programming that can be done to free the no longer used allocated memory space?


In one of the threads about this problem I suggested incorporating the corrected free function into existing code, and having a define along the lines of:

Code: [Select]
#define free myfree

in one of the header files that gets pulled into all libraries (including the String library). For the sake of a few dozen extra instructions you then have a workaround until the real library is fixed.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Sep 30, 2012, 12:29 am
Quote
But the bug in question is something to do with the first (or last?) allocated block not being freed correctly, or something like that.


Then is there any way in the code to keep that particular block filled with nothing?
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Sep 30, 2012, 03:23 am
I took what Andy wrote to be saying there is no bug/problem. My error.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: pYro_65 on Sep 30, 2012, 09:37 am

global variables work better for me and I haven't heard of a good reason I should avoid them in my Arduino sketches.
Lefty


I can share at least one reason why you should reconsider globals. Here is a little sketch showing how a global can cause a very bad day.

This calculates the square of 2.

Code: [Select]

   //A global
   int i = 666;

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

  void setup()
    {
      Squared< long > s_Square( 2 );
     
      Serial.begin( 9600 );
      Serial.print( "2 squared = " );
      Serial.print( s_Square.Result() );
      return;
    }
   
  void loop(){return;}
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 30, 2012, 10:13 am
Code: [Select]
   int i = 666;


I haven't even tried to run your sketch, but straight away, I sense trouble. ;)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: marcello.romani on Sep 30, 2012, 11:45 am
@pyro

You sketch is not about globals, rather it warns of the perils one faces by using poorly named variables :)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: pYro_65 on Sep 30, 2012, 12:28 pm
The name of the variable could be something intuitive, I used 'i' for this example.
It does not affect the example. The sketch below works fine.

Code: [Select]
   //A global
   int i = 666;

  void setup()
    {
      int i = 2 * 2;
     
      Serial.begin( 9600 );
      Serial.print( "2 squared = " );
      Serial.print( i );
      return;
    }
   
  void loop(){return;}


In my previous example,
The global is a non-dependant name, it is satisfying a rule where non-dependant names in dependant types take the last definition seen. The compiler doesn't consider the base class during name lookup, even though its 'i' is the closer scope. This is quite different to the snippet above where the closer scope is setup::i which is also used.

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.

As a minimum, place the global in a namespace so it has to be explicitly used.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: marcello.romani on Sep 30, 2012, 12:50 pm
Quote
As a minimum, place the global in a namespace so it has to be explicitly used.


Very interesting posts. I would add as a minimum don't go against common practices by naming a global with a name usually "reserved" for narrow-scoped counters and array indexes. IOW, don't produce code that's confusing to _read_ in the first place. :)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PaulS on Sep 30, 2012, 02:17 pm
Quote
You sketch is not about globals, rather it warns of the perils one faces by using poorly named variables

I see it more as a problem with having local and global variables with the same name, and making assumptions about which will be used when.

Having local and global variables of the same name is rarely a good idea.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 30, 2012, 10:55 pm
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:

Code: [Select]

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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Sep 30, 2012, 10:56 pm
A fairly simple fix in fact is to have a naming convention for class variables, eg.

Code: [Select]
int m_myVariable;   // m_ = member; or:
int myVariable_;    // using trailing underscore on class variables
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: pYro_65 on Oct 01, 2012, 01:27 am
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.
Quote
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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: fiddler on Nov 22, 2012, 09:43 am
Interesting and thank for clarifying this.

One question tho

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

Kim
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: marcello.romani on Nov 22, 2012, 09:56 am
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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: majenko on Nov 22, 2012, 11:24 am

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:

Code: [Select]


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 :)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: marcello.romani on Nov 22, 2012, 12:17 pm
:smiley-eek-blue:

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 ;)



(1) As in "Code Complete 2"; see Jeff Atwood's blog icon for an effective visual representation of the concept :)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Nov 22, 2012, 04:32 pm
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.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: fiddler on Nov 22, 2012, 07:49 pm
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" :-)

K
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PeterH on Nov 22, 2012, 08:34 pm

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


You're thinking of recursive code?
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Nov 23, 2012, 02:11 am
Right. Functions that call themselves or call others that call them, etc.

It's been a while since I wrote that kind of stuff.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: majenko on Nov 23, 2012, 11:11 am

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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Nov 23, 2012, 01:11 pm
Something else I wouldn't try with a 328P chip.

Long live the state machine!
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: majenko on Nov 23, 2012, 01:30 pm

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

Long live the state machine!


Hell yeah :)

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 ;)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: Delta_G on Nov 23, 2012, 06:47 pm
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."
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Nov 23, 2012, 08:54 pm

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!  :smiley-roll: Sad that the String class whiners almost never correct/adjust the usually simple they constantly whine about. Sad
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Nov 23, 2012, 09:07 pm
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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: Bachi on Jan 31, 2013, 06:11 pm
Regarding the String class, there is actually a method 'reserve()' that was added with 1.0.

Strange enough totally undocumented on http://arduino.cc/en/Reference/StringObject, but available: http://code.google.com/p/arduino/issues/detail?id=449

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.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Jan 31, 2013, 09:06 pm
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!
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Feb 01, 2013, 07:08 am

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.  ;)
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Feb 01, 2013, 08:16 am
When you're on a horse, you don't expect a steering wheel. An MCU is not a PC.

As far as not knowing C strings, once you understand what a char array with NULL terminator the rest with few exceptions is very straightforward. Yes there is some memorizing of shortened words but strcat() means string concatenation isn't hieroglyphs. Strtok() takes a bit of study but after writing your own array parsing it's no surprise at all. In fact I'd go so far as to say that writing your own version of any of those commands and then refining/rewriting to compact code will teach not only C string but better array handling and coding in general.

The old timers get that an MCU is not a PC. Many of us have written lots of code on both. We know to avoid the traps that the inexperienced walk right into and would rather save others from wasting their time.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: michinyon on Feb 01, 2013, 08:17 am
I find C++ Strings  annoying and unreliable on any platform.   In Java I use strings  but in C++ I use C-style char arrays.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: AWOL on Feb 01, 2013, 08:28 am
Quote
How is your world of horses, buggies, and telegrams/bag phones?

I saw a feature on TV recently about small-scale logging, using draught horses to extract the timber.
The horses could go where the tractors could not, and their impact on the environment much less, and they could work woodlands too small for ordinary commercial operations.

A reasonable analogy, I think.

The right tool/technique for the right problem.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: RudiAhlers on Jun 18, 2013, 01:00 pm
I'm fairly new to Arduino's, and C coding for that matter as well but have done PHP / MySQL / ASP / HTML coding before.

Can someone please explain to me, what to use if we shouldn't use strings? I see, in many forum posts, where people have problems they're simply being told "don't use strings", and more often than not no sample of an alternative piece of code is given for their problem so the OP in said forum thread doesn't learn anything. And, consequently other newbies don't learn anything useful either and still use strings, since most of the tutorials and forum sample code has them.


Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: wildbill on Jun 18, 2013, 01:22 pm
C++ Strings are objects that require memory allocation routines (malloc, free) to be called when they are used. For a long time, there was a bug in the implementation of free that the arduino uses that meant that you could run out of memory, so Strings were a dangerous proposition.

More recent versions of the IDE do not have this defect, but manipulating Strings still requires that you free and allocate memory. Given that arduinos have so little RAM, there is still the possibility that you will run out of space or fragment your free memory in such a way that there isn't a big enough contiguous piece to satisfy a request. At this point, your sketch will likely fail in unexpected ways, which will be hard to debug and when you ask for help on the forums you will be told "Don't use Strings".

There's nothing inherently wrong with C++ Strings if you have the memory for them and suitable error handling for dealing with low memory conditions, but for arduino, where you don't have these things, they are not sensible, particularly if your sketch is expected to run in perpetuity. As an alternative, arrays of char let you see exactly what memory you are using without any 'sleight of hand' stuff happening behind the scenes.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Jun 18, 2013, 01:46 pm

I see, in many forum posts, where people have problems they're simply being told "don't use strings" ...


There is a terminology problem here. The posts usually say don't use "String" (which is a specific class) not "don't use strings".

Depending on your requirements you can use an array of characters using the normal C functions like strcpy and strcat. A lot of the Arduinos only have 2048 bytes of RAM, so you don't have a lot of memory for arrays, dynamically allocated strings, or anything else.

At least with manually allocated arrays you know how much memory they are taking. And for constants, you can use strings directly from program memory, eg.

Code: [Select]

Serial.println (F ("hi there"));


Quote
... where people have problems  ...


Some problems are caused by the String class, when it had bugs. Some are caused even afterwards because of memory fragmentation. Some are unrelated. However when people post a lengthy sketch, which uses String extensively (as opposed to "strings") one of the first suggestions is to rework without using the String class.

Quote
... and more often than not no sample of an alternative piece of code is given for their problem ...


I have examples below which show receiving serial data. They stores strings into static buffers but do not use "String".

http://www.gammon.com.au/forum/?id=11425
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Jun 19, 2013, 02:10 am

Can someone please explain to me, what to use if we shouldn't use strings? I see, in many forum posts, where people have problems they're simply being told "don't use strings", and more often than not no sample of an alternative piece of code is given for their problem so the OP in said forum thread doesn't learn anything. And, consequently other newbies don't learn anything useful either and still use strings, since most of the tutorials and forum sample code has them.


Don't expect any of the "string" advocates to fix your code using the "string" functions, most likely due to the fact that the real code issue rarely ever has to do with using Strings. If persons can't wrap their head around the real issue, or are just stumped, they can always say "praise Jesus, drink more milk, and don't use Stings". Maybe the cure..., maybe not.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: Arrch on Jun 19, 2013, 02:19 am

Don't expect any of the "string" advocates to fix your code using the "string" functions


Give a man a fish...
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PeterH on Jun 19, 2013, 02:19 am

Don't expect any of the "string" advocates to fix your code using the "string" functions


... because there have been dozens, probably hundreds, of examples posted showing how to do this yourself and it is not hard.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Jun 19, 2013, 05:53 am

If persons can't wrap their head around the real issue, or are just stumped, they can always say "praise Jesus, drink more milk, and don't use Stings". Maybe the cure..., maybe not.


I can wrap my head around issues, thanks. When I see something obviously wrong, I mention it. If that doesn't fix it, we can move onto subtle things.

Example: If your care won't start, and it's out of petrol, that's probably the problem. Putting petrol in doesn't guarantee it will start, maybe the spark plugs are bad, but let's fix the simple stuff first.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: RudiAhlers on Jun 19, 2013, 09:20 am


I see, in many forum posts, where people have problems they're simply being told "don't use strings" ...


My bad..... I didn't see the Capital S in "Strings", and misread it as "strings"
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Jun 19, 2013, 07:08 pm
328P has 2k for heap and stack. That's not much room to have storage elements that copy themselves just to add 1 character, not to mention what that could do in time-critical apps.

I see the String Object school play down awareness of both hardware and what the software is doing. They learn to use/reply on wasteful black boxes and don't bother with simpler more direct code. That's okay when you're not pushing much more than halfway to limits. But step much beyond and then your ability to implement solutions begins with learning what you avoided for however long you did including different approaches to solutions that go with all that. 

It's easier to match the tools (commands and techniques) to the environment. AVR's tend to be very small to small. What is trivial on a PC is not always going to fit well on an AVR. A bathtub that goes well in a house will be a poor fit on a bicycle regardless of the fact that yes you can fit a bathtub on a bicycle. Oh well let's be reasonable and just fit on a sink instead shall we?

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: nickgammon on Jun 19, 2013, 11:07 pm

Don't expect any of the "string" advocates to fix your code using the "string" functions, most likely due to the fact that the real code issue rarely ever has to do with using Strings. If persons can't wrap their head around the real issue, or are just stumped, they can always say "praise Jesus, drink more milk, and don't use Stings". Maybe the cure..., maybe not.


Better read this then:

http://forum.arduino.cc/index.php?topic=172156.msg1284047#msg1284047


So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently.


Another happy customer, once he removed "String".
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Jun 20, 2013, 01:39 am


Don't expect any of the "string" advocates to fix your code using the "string" functions, most likely due to the fact that the real code issue rarely ever has to do with using Strings. If persons can't wrap their head around the real issue, or are just stumped, they can always say "praise Jesus, drink more milk, and don't use Stings". Maybe the cure..., maybe not.


Better read this then:

http://forum.arduino.cc/index.php?topic=172156.msg1284047#msg1284047


So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently.


Another happy customer, once he removed "String".


What was really said :

Quote
So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently. The flicker that remained ended up a simple fix. I shortened the length of the wires connecting the buffers as much as physically possible (approximately an inch). I am still unsure why it only was a problem with the Uno and not the Duemilanove. Regardless, it works perfectly on both now. I am attaching a photo of the 1st panel running on the Raspberry Pi with an Uno (Left) and a Duemilanove (Right). All addresses work perfectly and no flicker.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PeterH on Jun 20, 2013, 05:14 pm

What was really said :

Quote
So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently. The flicker that remained ended up a simple fix. I shortened the length of the wires connecting the buffers as much as physically possible (approximately an inch). I am still unsure why it only was a problem with the Uno and not the Duemilanove. Regardless, it works perfectly on both now. I am attaching a photo of the 1st panel running on the Raspberry Pi with an Uno (Left) and a Duemilanove (Right). All addresses work perfectly and no flicker.



The inconsistent behaviour was resolved by removing the use of the problematic String class, as Nick Gammon indicated by his quote. The fact that there was also a hardware issue causing flickering is hardly relevant to this thread.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: GoForSmoke on Jun 20, 2013, 06:00 pm


What was really said :

Quote
So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently. The flicker that remained ended up a simple fix. I shortened the length of the wires connecting the buffers as much as physically possible (approximately an inch). I am still unsure why it only was a problem with the Uno and not the Duemilanove. Regardless, it works perfectly on both now. I am attaching a photo of the 1st panel running on the Raspberry Pi with an Uno (Left) and a Duemilanove (Right). All addresses work perfectly and no flicker.


I'm not satisfied with clues but that comes from years of debugging. So I went to that thread and ASKED the poster what that meant. Apparently Strings are Arduino code where char arrays are C++ to the beginner.
There's also the "reworking" part that you might have 'got' if you had looked at the original String-based code and the posts saying to ditch Strings, etc, that should have told you that the post you quoted does NOT actually support your view.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: zoomkat on Jun 21, 2013, 02:11 am


What was really said :

Quote
So reworking the arduino code to utilize C++ chars rather than strings seems to have solved the problem with it acting inconsistently. The flicker that remained ended up a simple fix. I shortened the length of the wires connecting the buffers as much as physically possible (approximately an inch). I am still unsure why it only was a problem with the Uno and not the Duemilanove. Regardless, it works perfectly on both now. I am attaching a photo of the 1st panel running on the Raspberry Pi with an Uno (Left) and a Duemilanove (Right). All addresses work perfectly and no flicker.



The inconsistent behaviour was resolved by removing the use of the problematic String class, as Nick Gammon indicated by his quote. The fact that there was also a hardware issue causing flickering is hardly relevant to this thread.


Perhaps you missed the "I am still unsure why it only was a problem with the " part above. Obviously the OP had two different hardware setups (Uno and Duemilanove) on which he was originally getting different results. He made both code and hardware changes on the uno setup and got his desired results. Probably different microcontrollers on the two boards with different memory capacities requiring "string" usage on the one with less memory or less efficient memory handling.

Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: PeterH on Jun 21, 2013, 03:02 am
If you're getting memory corruption then even trivial changes could potentially affect the symptoms - simply choosing a different board type could be enough, if it affects the resulting executable image.
Title: Re: The HATRED for String objects - "To String, or not to String"
Post by: Docedison on Jun 21, 2013, 07:05 am
In the posted PDF of the TLC5490 driver there are several errors that could well be a part of the issue. The power supply is a 7805 with minimal I/O bypassing, there is no current limiter/CC supply for the LED's and the comment about lead length's is really snarky... rather than indicate a fix it indicates bypassing problems and/or a lack of terminations (Pull-ups/downs) on the control lines. While Strings are certainly responsible for many of the issues, that the completed design works at all is to me a wonder.. I had to make those mistakes myself many years ago and learn from them.

Doc