best practice: switch or if / else for about 33 different String values

I am looking for general best practice wisdom for embedded code devices such as ESP8266

I have a function that handles about 33 different commands.

Would the code be more compact or faster if I used a switch / case statement or a long list of if/else if statements?

if (x == "{A}") {
...
}

else if (x == "{B}") {
...
}

// OR

switch (x) {
  case "{A}" : {
  ...
  break;
  }
  case "{B}" : {
  ...
  break;
  }

} // switch x

Lets assume all the cases will need a break statement, no fall throughs...

C / C++ has no valid switch / case syntax for strings.

have a function that handles about 33 different commands.
Would the code be more compact or faster...

If that is truly your concern a minimal perfect hash is the correct choice.

It's a bit more advanced but in the end cleaner so I'll suggest neither and instead use an array of function pointers to individual handlers per command.

So for example:

#define MIN_CMD    0
#define MAX_CMD    2  //2 for this example

typedef void (* CommandHandler)(int);

CommandHandler Commands[MAX_CMD+1] = {&Command0, &Command1, &Command2};

In the above, CommandHandler is a type defined as being a pointer to a function that returns void and accepts an integer parameter input.

Commands[] is an array of type CommandHandler (i.e. an array of function pointers); Commands[0] points to a command handler called when command '0' is received. Commands[1] points to a function you'd want called for command '1' etc. You'd create each handler like this:

void Command0( int parameter )
{
    //do stuff for command '0' here

}//Command0

void Command1( int parameter )
{
    //do stuff for command '1' here

}//Command1

void Command2( int parameter )
{
    //do stuff for command '2' here

}//Command2

You code would look at the command received, verify it's valid (i.e. recognized) and then call a function from the pointer array:

void loop()
{

.
.
.

    if( ReceivedCommand >= MIN_CMD && ReceivedCommand <= MAX_CMD )
    {
        Commands[ReceivedCommand](Parm);

    }//if
    else
    {
        //command not recognized; warn user etc...
    }//else
}//loop

Here's a working example framework I just ran a MKR WiFi 1010 I had lying around (should be fine on whatever you're using...)

#define MIN_CMD 0
#define MAX_CMD 2

typedef void (* CommandHandler)(int);

//prototypes
void Command0( int parm );
void Command1( int parm );
void Command2( int parm );

CommandHandler Commands[MAX_CMD+1] = 
{
    &Command0, 
    &Command1, 
    &Command2
};


void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  while( !Serial );
  
}//setup

void loop() 
{
    ProcessCommand(0, 1);
    ProcessCommand(1, 465);
    ProcessCommand(2, 1000);
    ProcessCommand(4, 0);       //should generate an unrecognized command error
    
    delay(1000);
    
}//loop

void ProcessCommand( int Cmd, int Parm )
{
    if( Cmd < MIN_CMD || Cmd > MAX_CMD )
    {
        Serial.println( "*** ERROR Unrecognzied command ***" );
        
    }//if
    else
    {
        Commands[Cmd](Parm);
        
    }//else
}//ProcessCommand

//Command handlers
void Command0( int parm )
{
    Serial.print( "Command 0: " );
    Serial.println (parm );
}//Command0

void Command1( int parm )
{
    Serial.print( "Command 1: " );
    Serial.println (parm );
}//Command1

void Command2( int parm )
{
    Serial.print( "Command 2: " );
    Serial.println (parm );
}//Command2

I think this would generate the cleanest and probably pretty quick code.

@OP
If your commands are just one character (ignoring { and }), you can use a switch/case or an approach as suggested by @Blackfin.

This is a slightly modified version of @Blackfin's loop

void loop()
{
  String a = "{A}";

  Serial.println(a[1]);
  Serial.println(b[1]);

  ProcessCommand(a[1] - 'A', 1);
  ProcessCommand(b[1] - 'A', 465);

  delay(2000);

}//loop

The code takes the actual command character, subtracts 'A' to create the index in the array and uses that as argument for ProcessCommand.

You will need some more intelligence; not all uppercase letters might be in use, you might have lowercase letters, numbers and special symbols.

If the Strings are longer, you probably have to add some more intelligence.

Note:
you should forget that String (capital S) exists in Arduino's language. Extensive use of String objects (specifically concatenations) can leave holes in you memory that can result in crashes at run time.

AFAIK the choice between switch/case and if/else is only for the convenience of the programmer. I am not aware that it makes any significant difference to the code the compiler produces.

...R

With the AVR processor it makes no difference.

Some compiler / processor combinations generate a calculated indirect jump (e.g. Microsoft's compiler does that).

thanks all.

I should have known that with C++ there will be umpteen ways to do something (but not the seemingly logical one)

My commands are varying lengths, and the parameters for each command are complex and varied strings. I would have to start passing strings to the various functions too.

Since my if statements are working well at present, I will continue using them until I find a need for the pointers to functions approach.

Many thanks!

Personally I find that switch/case used with descriptive case names from an enum is easier to write, read and maintain that multiple if statements particularly when there is more than one level of if and the odd else thrown in. However, switch/case only works where the cases are integers or can be expressed as integers which is not always easy or possible.

Actually, that is not correct. When the cases numeric values are close together, the compiler (tested on avr-g++ 4.9.2) will generate a jumptable. So for a large number of cases, switch/case is faster.

Excellent. Let's have a look...

void setup() 
{
  Serial.begin( 250000 );
}


void loop() 
{
  static int which;

  switch ( which ) {
    case 0: 
      Serial.println(F("zero")); 
      break;
    case 1: 
      Serial.println(F("one")); 
      break;
    case 2: 
      Serial.println(F("two")); 
      break;
    case 3: 
      Serial.println(F("three")); 
      break;
    case 4: 
      Serial.println(F("four")); 
      break;
    case 5: 
      Serial.println(F("five")); 
      break;
  }
  ++which;

  if ( which > 5 )
  {
    which = 0;
  }
}
...
void loop() 
{
  static int which;

  switch ( which ) {
 57c:	80 91 16 01 	lds	r24, 0x0116	; 0x800116 <__data_end>
 580:	90 91 17 01 	lds	r25, 0x0117	; 0x800117 <__data_end+0x1>
 584:	82 30       	cpi	r24, 0x02	; 2
 586:	91 05       	cpc	r25, r1
 588:	a1 f0       	breq	.+40     	; 0x5b2 <main+0x126>
 58a:	3c f4       	brge	.+14     	; 0x59a <main+0x10e>
 58c:	00 97       	sbiw	r24, 0x00	; 0
 58e:	71 f0       	breq	.+28     	; 0x5ac <main+0x120>
 590:	01 97       	sbiw	r24, 0x01	; 1
 592:	c9 f4       	brne	.+50     	; 0x5c6 <main+0x13a>
    case 0: 
      Serial.println(F("zero")); 
      break;
    case 1: 
      Serial.println(F("one")); 
 594:	8c e7       	ldi	r24, 0x7C	; 124
 596:	90 e0       	ldi	r25, 0x00	; 0
 598:	14 c0       	rjmp	.+40     	; 0x5c2 <main+0x136>

void loop() 
{
  static int which;

  switch ( which ) {
 59a:	84 30       	cpi	r24, 0x04	; 4
 59c:	91 05       	cpc	r25, r1
 59e:	79 f0       	breq	.+30     	; 0x5be <main+0x132>
 5a0:	5c f0       	brlt	.+22     	; 0x5b8 <main+0x12c>
 5a2:	05 97       	sbiw	r24, 0x05	; 5
 5a4:	81 f4       	brne	.+32     	; 0x5c6 <main+0x13a>
      break;
    case 4: 
      Serial.println(F("four")); 
      break;
    case 5: 
      Serial.println(F("five")); 
 5a6:	88 e6       	ldi	r24, 0x68	; 104
 5a8:	90 e0       	ldi	r25, 0x00	; 0
 5aa:	0b c0       	rjmp	.+22     	; 0x5c2 <main+0x136>
{
  static int which;

  switch ( which ) {
    case 0: 
      Serial.println(F("zero")); 
 5ac:	80 e8       	ldi	r24, 0x80	; 128
 5ae:	90 e0       	ldi	r25, 0x00	; 0
 5b0:	08 c0       	rjmp	.+16     	; 0x5c2 <main+0x136>
      break;
    case 1: 
      Serial.println(F("one")); 
      break;
    case 2: 
      Serial.println(F("two")); 
 5b2:	88 e7       	ldi	r24, 0x78	; 120
 5b4:	90 e0       	ldi	r25, 0x00	; 0
 5b6:	05 c0       	rjmp	.+10     	; 0x5c2 <main+0x136>
      break;
    case 3: 
      Serial.println(F("three")); 
 5b8:	82 e7       	ldi	r24, 0x72	; 114
 5ba:	90 e0       	ldi	r25, 0x00	; 0
 5bc:	02 c0       	rjmp	.+4      	; 0x5c2 <main+0x136>
      break;
    case 4: 
      Serial.println(F("four")); 
 5be:	8d e6       	ldi	r24, 0x6D	; 109
 5c0:	90 e0       	ldi	r25, 0x00	; 0
      break;
    case 5: 
      Serial.println(F("five")); 
 5c2:	0e 94 52 01 	call	0x2a4	; 0x2a4 <_ZN5Print7printlnEPK19__FlashStringHelper.constprop.6>
      break;
  }
  ++which;
 5c6:	80 91 16 01 	lds	r24, 0x0116	; 0x800116 <__data_end>
 5ca:	90 91 17 01 	lds	r25, 0x0117	; 0x800117 <__data_end+0x1>
 5ce:	01 96       	adiw	r24, 0x01	; 1

  if ( which > 5 )
 5d0:	86 30       	cpi	r24, 0x06	; 6
 5d2:	91 05       	cpc	r25, r1
 5d4:	2c f4       	brge	.+10     	; 0x5e0 <main+0x154>
      break;
    case 5: 
      Serial.println(F("five")); 
      break;
  }
  ++which;
 5d6:	90 93 17 01 	sts	0x0117, r25	; 0x800117 <__data_end+0x1>
 5da:	80 93 16 01 	sts	0x0116, r24	; 0x800116 <__data_end>
 5de:	04 c0       	rjmp	.+8      	; 0x5e8 <main+0x15c>

  if ( which > 5 )
  {
    which = 0;
 5e0:	10 92 17 01 	sts	0x0117, r1	; 0x800117 <__data_end+0x1>
 5e4:	10 92 16 01 	sts	0x0116, r1	; 0x800116 <__data_end>
	
	setup();
    
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
 5e8:	0e 94 98 01 	call	0x330	; 0x330 <_Z14serialEventRunv>
 5ec:	c7 cf       	rjmp	.-114    	; 0x57c <main+0xf0>
...

No jump table. No calculated indirect jump.

The optimizer does generate code to do a binary search. With only six cases that won't make a difference but with more it will.

Very well.

void test(int a)
{
 switch (a) {
 case 0: PORTB = 1; break;
 case 1: PORTB = 2; break;
 case 2: PORTB = 3; break;
 case 3: PORTB = 4; break;
 case 4: PORTB = 5; break;
 case 5: PORTB = 6; break;
 case 6: PORTB = 7; break;
 }
}

Port access was inserted to avoid optimization.

   switch (a) {
0000004F  CPI R24,0x07 Compare with immediate 
00000050  CPC R25,R1 Compare with carry 
00000051  BRCC PC+0x1A Branch if carry cleared 
00000052  MOVW R30,R24 Copy register pair 
00000053  SUBI R30,0xCC Subtract immediate 
00000054  SBCI R31,0xFF Subtract immediate with carry 
00000055  JMP 0x00000073 Jump 
   case 0: PORTB = 1; break;
00000057  LDI R24,0x01 Load immediate 
00000058  OUT 0x05,R24 Out to I/O location 
00000059  RET Subroutine return 
   case 1: PORTB = 2; break;
0000005A  LDI R24,0x02 Load immediate 
0000005B  OUT 0x05,R24 Out to I/O location 
0000005C  RET Subroutine return 
   case 2: PORTB = 3; break;
0000005D  LDI R24,0x03 Load immediate 
0000005E  OUT 0x05,R24 Out to I/O location 
0000005F  RET Subroutine return 
   case 3: PORTB = 4; break;
00000060  LDI R24,0x04 Load immediate 
00000061  OUT 0x05,R24 Out to I/O location 
00000062  RET Subroutine return 
   case 4: PORTB = 5; break;
00000063  LDI R24,0x05 Load immediate 
00000064  OUT 0x05,R24 Out to I/O location 
00000065  RET Subroutine return 
   case 5: PORTB = 6; break;
00000066  LDI R24,0x06 Load immediate 
00000067  OUT 0x05,R24 Out to I/O location 
00000068  RET Subroutine return 
   case 6: PORTB = 7; break;
00000069  LDI R24,0x07 Load immediate 
0000006A  OUT 0x05,R24 Out to I/O location 
0000006B  RET Subroutine return 

[...]

00000073  LSL R30		Logical Shift Left 
00000074  ROL R31		Rotate Left Through Carry 
00000075  LPM R0,Z+		Load program memory and postincrement 
00000076  LPM R31,Z		Load program memory 
00000077  MOV R30,R0		Copy register 
00000078  IJMP 		Indirect jump to (Z)

However, it seems that the threshold for inserting the jumptable (or the offset calculation) is actually >6 instead of >5.