Adding "case" to "switch" uses "Global variables" space

It does not make since to me but adding a simple case greatly increased the SRAM usage.

I am using the IRLIB2 in an effort to decode as many different IR remotes as possible. I am decoding the IR signal and then sending text to the serial port indicating the value decoded. I am using a large switch to translate the decoded value to the text.

The problem is that adding cases increased SRAM usage for no know reason. The code is currently several hundred lines in length. I also combine several statement on a line. It is a lot of code.
Here is a segment of the code

extern const char F_0[]                  PROGMEM = "0";
extern const char F_1[]                  PROGMEM = "1";
extern const char F_2[]                  PROGMEM = "2";
extern const char F_3[]                  PROGMEM = "3";
. . . 
const char *F_Cmd=NULL;  
. . . 
switch (My_Decoder.value & 0xFF) {
    case 0x00: F_Cmd = F_0;               break;
    #if 0
    case 0x01: F_Cmd = F_1;               break;
    case 0x02: F_Cmd = F_2;               break;
    case 0x03: F_Cmd = F_3;               break;
    case 0x04: F_Cmd = F_4;               break;

When I compile it get:
Sketch uses 17080 bytes (52%) of program storage space. Maximum is 32256 bytes.
Global variables use 1574 bytes (76%) of dynamic memory, leaving 474 bytes for local variables. Maximum is 2048 bytes.

extern const char F_0[]                  PROGMEM = "0";
extern const char F_1[]                  PROGMEM = "1";
extern const char F_2[]                  PROGMEM = "2";
extern const char F_3[]                  PROGMEM = "3";
. . . 
const char *F_Cmd=NULL;  
. . . 
switch (My_Decoder.value & 0xFF) {
    case 0x00: F_Cmd = F_0;               break;
    case 0x01: F_Cmd = F_1;               break;
    #if 0
    case 0x02: F_Cmd = F_2;               break;
    case 0x03: F_Cmd = F_3;               break;
    case 0x04: F_Cmd = F_4;               break;

When I compile it get:
Sketch uses 17092 bytes (52%) of program storage space. Maximum is 32256 bytes.
Global variables use 1574 bytes (76%) of dynamic memory, leaving 474 bytes for local variables. Maximum is 2048 bytes.

Adding a "case" made a sketch size increase by 12 bytes (reasonable) and no change in the "Global variable" use (expected).

extern const char F_0[]                  PROGMEM = "0";
extern const char F_1[]                  PROGMEM = "1";
extern const char F_2[]                  PROGMEM = "2";
extern const char F_3[]                  PROGMEM = "3";
. . . 
const char *F_Cmd=NULL;  
. . . 
switch (My_Decoder.value & 0xFF) {
    case 0x00: F_Cmd = F_0;               break;
    case 0x01: F_Cmd = F_1;               break;
    case 0x02: F_Cmd = F_2;               break;
    #if 0
    case 0x03: F_Cmd = F_3;               break;
    case 0x04: F_Cmd = F_4;               break;

When I compile it get:
Sketch uses 17072 bytes (52%) of program storage space. Maximum is 32256 bytes.
Global variables use 2082 bytes (101%) of dynamic memory, leaving -34 bytes for local variables. Maximum is 2048 bytes.

Adding another "case" made a sketch size decrease by 20 bytes (unexpected) and increase in the "Global variable" use by 508 bytes (VERY unexpected).

Seem to be missing some breaks there.

What's in those extern arrays? You know if you don't use one it doesn't get linked in. So if you add a case that suddenly uses one that wasn't previously referenced then your code should grow by the size of the array.

INTP:
Seem to be missing some breaks there.

Scroll right.

Ah, didn't see them when I was viewing on the phone.

As mentioned, compiler does some optimizing, possibly things were getting cut out when smaller to make you think the smaller was your default prog size.

Delta_G:
What's in those extern arrays? You know if you don't use one it doesn't get linked in. So if you add a case that suddenly uses one that wasn't previously referenced then your code should grow by the size of the array.

The complete variable definition is in the code block. The arrays are single character strings.

INTP:
Ah, didn't see them when I was viewing on the phone.

As mentioned, compiler does some optimizing, possibly things were getting cut out when smaller to make you think the smaller was your default prog size.

Program size does not seem to be the problem. The problem is RAM (Global Variable) usage. Code is added that uses no new variables and usage jumps (a lot).

extern const char F_0[]                  PROGMEM = "0";

extern with an initialization. That looks suspicious.

Is F_0 declared in more than one compilation unit?

Aha. The odd formatting caused me not to see the initializations. What's the purpose of an array if it only has one element? Something seems fishy between that and them being extern.

Not yet but that is the intent.

Do you know why this would cause the observed behavior?

Not yet but that is the intent.

Do you know why this would cause the observed behavior?

Delta_G:
Aha. The odd formatting caused me not to see the initializations. What's the purpose of an array if it only has one element? Something seems fishy between that and them being extern.

This is just part of the code. Other definitions hold strings like "Power", "Channel Up" and "Input".
Is there something about short strings using more RAM than long strings?

Is there something about short strings using more RAM than long strings?

A string uses one byte per character, regardless of the length of the string.

A String, on the other hand, wastes proportionately more space for short strings than for long strings. The key is "proportionately". A String wastes the same amount of memory for every String in addition to what is required to hold the string.

PaulS:
A string uses one byte per character, regardless of the length of the string.

A String, on the other hand, wastes proportionately more space for short strings than for long strings. The key is "proportionately". A String wastes the same amount of memory for every String in addition to what is required to hold the string.

It was not by accident that I indicate that it was a "string" and not a "String". Also the definition is clearly a character array and not a String object.

Not to split hairs, but a string also includes a NULL terminator. Therefore, the string "1" is a 2 byte character array.

This is all interesting. But does not explain what is happening.

RandallR:
Do you know why this would cause the observed behavior?

You first need to determine if it does cause the observed behaviour.

Does removing "extern" have an effect?

Exactly the same results.

Also tried replacing "extern" with "static". Same results.

RandallR:
Do you know why this would cause the observed behavior?

I think I do. When you go from two entries to three it sees that you are setting the same pointer to different addresses based on the value of a byte. I suspect it is creating a table of 256 addresses and just indexes into the table. That would add 512 bytes but saves a few over the previous incarnation for the net gain is 508.
Try changing the 0xFF to 0x07. That should allow the compiler to generate an 8-entry table (16 bytes) instead of a 256-entry table.

johnwasser:
I think I do. When you go from two entries to three it sees that you are setting the same pointer to different addresses based on the value of a byte. I suspect it is creating a table of 256 addresses and just indexes into the table. That would add 512 bytes but saves a few over the previous incarnation for the net gain is 508.
Try changing the 0xFF to 0x07. That should allow the compiler to generate an 8-entry table (16 bytes) instead of a 256-entry table.

Exactly the same results.

This is some what comforting. Since a switch could have hundreds of cases. To build the jump table in RAM would eat a lot of memory.

You need to either make the whole sketch available, or duplicate the memory usage situation in a smaller sketch (one that actually compiles, and demonstrates the problem.) (or, you could publish the .elf file, I guess, if you feel the source is sensitive.)
This is a problem that ought to be analyzable by poking at the object files with tools like avr-nm and avr-objdump, but ... I'd need to have the object files... (source code, however big, is probably smaller than two .elf files :slight_smile: )

I can't simulate the behaviour. Modified the dump example and compiled with IDE1.6.6

#include "IRLibAll.h"

IRrecvPCI myReceiver(2); //create receiver and pass pin number
IRdecode myDecoder;   //create decoder

void setup()
{
  Serial.begin(250000);
  delay(2000); while (!Serial); //delay for Leonardo
  myReceiver.enableIRIn(); // Start the receiver
  Serial.println(F("Ready to receive IR signals"));
}

void loop()
{
  const char F_0[] PROGMEM = "0";
  const char F_1[] PROGMEM = "1";
  const char F_2[] PROGMEM = "2";
  const char F_3[] PROGMEM = "3";

  const char *F_Cmd = NULL;

  //Continue looping until you get a complete signal received
  if (myReceiver.getResults())
  {
    myDecoder.decode();           //Decode it

    switch (myDecoder.value & 0xFF)
    {
      case 0x00: F_Cmd = F_0; break;
      case 0x01: F_Cmd = F_1; break;
      case 0xFF: F_Cmd = F_2; break;
    }

    Serial.println(F_Cmd);

    myReceiver.enableIRIn();      //Restart receiver
  }
}

Changing the 3rd case to 0x02 or removing the 3rd case makes no difference on global variables.

Board: Uno
IDE: 1.8.2
38 khz receiver (such as V1838B or TL1838) plugged into pins 3, 4, and 5 of Uno

Code snippet from around line 660.

GlobalVariableProblem.zip (84.5 KB)