6

I am rewriting a set of LED manipulation functions to be more object oriented, and I have found that, mysteriously, my global variable memory space has been over consumed, despite a reduction of the use of global variables.

I played around with a number of things, and found something curious.

Here is a fragment of a function that does not live in an object, and is called directly from loop():

Animation *getNewAnim(int index)
{
    switch(index)
    {
        case 0:
            return new RedClouds();
        case 1:
            return new TwoLayerClouds();
        case 2:
            return new LavaLamp1();
        case 3:
            return new Bubbles1();

        ...  (20 or so more cases)
     }

This is a function that lets me switch between display modes based on the value of index. Only one of these objects is ever created at a given time; I delete the old one before calling this, so there will never be multiple instances in use simultaneously.

Now the curious thing is that if I remove a few of these return statements, collapsing the block into something like this:

Animation *getNewAnim(int index)
{
    switch(index)
    {
        case 0:
        case 1:
        case 2:
        case 3:
            return new Bubbles1();
}

The amount of global memory space used falls considerably.

My hypothesis is that the Arduino build environment is pre-allocating space for each of these objects in the global memory space before the program is even run, rather than doing so dynamically as needed.

Is my hypothesis correct? And if so, is there any way to tell the build environment not to do this, as I'd rather use the same memory for my new object that was used by the old one.

To clarify: When I build I see this sort of output:

Sketch uses 6,550 bytes (21.3%) of program storage space. Maximum is 30,720 bytes.
Global variables use 1,167 bytes (57.0%) of dynamic memory, leaving 881 bytes for local variables. Maximum is 2,048 bytes.

When I refer to "global memory" I am referring to the Global variables number on that output. I'll also note that when I compile with the second example, the program storage space reported also falls dramatically.

NEW HYPOTHESIS

Here is what I think may be going on. When I omit the classes from the switch statement, the compiler seems to optimize them out of the codebase, which is what I expect, and is consistent with seeing the program storage space shrink. Each class has a method that returns it's name. This method contains an embedded string, and I now believe that this string is stored in the global variable space. Therefore, if the class is included, more global variable space is consumed to hold the name, and if it is optimized out, less is consumed. This theory is consistent with all the facts I have, and makes more sense than my previous hypothesis.

watusimoto
  • 61
  • 2
  • 1
    Are you referring to PROGMEM or RAM? Please post a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve). I don't see how the "build environment" (basically g++) could possibly pre-allocate space for objects which are going to be dynamically allocated anyway. How are you measuring "global memory space", and what do you mean by that precisely? – Nick Gammon Feb 01 '16 at 05:08
  • `return new Bubbles1();` - why the brackets? Why not: `return new Bubbles1;` ? – Nick Gammon Feb 01 '16 at 05:28
  • @Nick: I added a bit at the bottom of my question that I hope clarifies. As to the ()... good question. I'll look into that. – watusimoto Feb 01 '16 at 05:52
  • 1
    Without seeing the rest of your code, but only a [snippet](http://snippets-r-us.com/) it is hard to say. Maybe the other classes have static variables. If the compiler does not ever see them instantiated it might omit the static variables. – Nick Gammon Feb 01 '16 at 06:00
  • The shrinking program storage makes sense; if the class is never instantiated, the linker probably doesn't bother include the code. However, I tried adding more statics to both a class and some of its methods, and that didn't affect the global memory space at all (which is what I would expect). If I can get an unrelated problem solved, and make sure I'm ok license-wise, I could probably upload the whole thing to github. – watusimoto Feb 01 '16 at 06:15
  • It's common for people to ask "how come the program gets much bigger when I **call** a function?". From their viewpoint, they only added one line of code. From the compiler's viewpoint the things that were called are now needed, whereas before they were not. – Nick Gammon Feb 01 '16 at 06:20
  • I should point out that this is a bit of a made-up problem. You are asking: "If I omit some code (which in practice I cannot) why does the executable get smaller and use less RAM?". It's a hypothetical problem. You *won't* be omitting the code. And, there is no compiler switch along the lines of "don't waste memory". It is already a highly optimizing compiler. It tends to do things which look strange if you don't realize that. – Nick Gammon Feb 01 '16 at 06:23
  • 2
    It's theoretical in one sense, but if I can understand why what I'm seeing is happening, I can design around the problem. As it is, I am just guessing at what's going on in the compiler, and, as you say, it doesn't always behave as expected. What I need is a tool to tell me what the compiler is putting into the global variable space. – watusimoto Feb 01 '16 at 06:38
  • The Animation class defines a number of virtual member functions. The linker has a hard time removing virtual member functions that are not used/called as there is a reference in the virtual table. If the functions use string literals these are all allocated in "global variables". That is why it is important to use the F() macro. – Mikael Patel Feb 01 '16 at 07:27
  • http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_flashstrings – Mikael Patel Feb 01 '16 at 10:48
  • see also http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_varinit – Mikael Patel Feb 01 '16 at 12:10
  • 2
    Try running `avr-nm -Crtd --size-sort` on your elf file. Look for symbols of type 'D' (initialized data), 'B' (BSS) and 'V' (vtable), in either upper or lower case. The list you get is sorted by size. Literal strings may be missing from this list, but you see them with `avr-objdump -s -j .data`. – Edgar Bonet Feb 01 '16 at 14:56

1 Answers1

1

I suggest you take a look at https://stackoverflow.com/questions/16274451/operator-new-for-arduino the new operator is not coming from the c++ libraries, it is coming from the Arduino libraries. It's a wrapper for malloc Is using malloc() and free() a really bad idea on Arduino?

TomKeddie
  • 227
  • 2
  • 3