Use of Goto to exit loops?

Asking all super-dev geeks who know how the Particle chips work. In my embedded microprocessor programming experience, I know how easy it is to lose control of the stack in assembler. My question: Say I’m in a “while()” loop, and jump out with a “goto”, because a “break” won’t exit far enough? Will that foul the Particle Core/Photon’s stack?

Very simply:

while (x < 5)
{
x++;
switch (x)
{
case 1: …
break;
case 2: …
break;
case 3:
if (TOut) goto Fail;
break;
default: …
}
}

return 1;

Fail: Serial.println(“Routine failed!”);
return 0;

I don’t know how the stack is implemented on the Particle chips, but in my case, that would foul the stack, as the WHILE arguments would never get popped off. And when the stack runs out of control, a hard reset usually follows.

P.S. I’m just curious, as code can usually be written a couple of different ways.

That’s valid C, and should just work, the compiler will take care of fixing up the stack.

If you are having problems, I suspect the root cause lies elsewhere.

I’m not having problems (of that sort, anyway!)…but was just curious if it would be a potential problem. Thanks.

@WebDust21, goto!!! Last time I used a goto was in Fortran and assembler (I am dating myself). You don’t need a goto, just do the Serial.print and the return right in the switch statement. When the code exits, the stack pointer will be restored as it should be. :wink:

case 3:
  if (TOut) {
     Serial.println("Routine failed!");
     return 0;
  }
break;
1 Like

OK - As @peekay123 points out for 99%+ of user code situations, you can write efficient code without using goto, but it still can have a place in some very specialized (usually system-level or protocol-related) areas.

Anyway, if it’s legal C, the compiler should just do the right thing, regardless of coding style; after all isn’t that why we use them instead of assemblers ?

3 Likes

@peekay123: You’re absolutely right in that code snippet. That wasn’t at all the code I was working with–I was just trying to ask a question as simply as possible.
What, GOTO is the equivalent of carbon dating? I love programming in assembler…Microchip PIC10,12,16,18, dsPIC30, Zilog Z80, ARM7TDMI…but let’s not add Particle Core to that list just yet :grinning:

P.S. The ARM7, PIC18 and dsPIC30 don’t have “GOTO” instructions…they “BRANCH” instead. Never mind!

1 Like

Tougher question: What if I “GOTO” (5th line) a label inside of a 2nd-level nested “switch()” “case:” (“rulesNoHalt”), and the code is followed by “break;” Which switch() will be exited, the 2nd level, or the 1st level? (I know, I’ll find out as soon as I compile it! I’m probably asking baby C questions, too…)

Here’s my code:

        switch (strWork[0])
        {
          case 'c':
            if (jTask == 3) WriteEEByte(cfgSysFlags, cWork);
            if ((jTask == 4) && (jCount % 8 != 0)) {jCount++; goto rulesNoHalt;}
            // Just fall through.
          case 'r':
            if (jTask == 4)
            {
              if (!(jCount / 8)) break;   // exits this SWITCH{} on hidden copy.
              cBase = cfgRules + ((jCount / 8) * cfgRulesSize);   // calculate the root address

              switch (jCount % 8)   // 8 options
              {
                case 0:                                   //0 -> -> CH
                case 3:                                   //3 -> -> TV
                case 6: cWork = atoi(strBuf); break;      //6 -> -> Action
                case 1: WriteEEByte(cBase + cfgRulesCH, cWork |= (atoi(strBuf) << 6)); break;     // -> -> CD
                case 2: WriteEEWord(cBase + cfgRulesVal, atoi(strBuf)); break;                    // -> -> V
                //case 3 is above--same code as #0.
                case 4: WriteEEByte(cBase + cfgRulesWait, cWork |= (atoi(strBuf) << 6)); break;   // -> -> TR
                case 5: WriteEEString(cBase + cfgRulesMsg, strBuf); break;                        // -> -> Action
                //case 6 is above, same as #0, 3
                case 7: //NOTE: can be missing!            // -> -> Action
                  cWork |= 128;   // It's here!
rulesNoHalt:      WriteEEByte(cBase + cfgRulesAction, cWork);
                  break;

                default: err = (char*)dpErr2;
              }
            }
            else
              if (++jTask == 4) {jCount = 0;} else {err = (char*)dpErr2;}     // will cause a loop exit.

            break;
          //--------------------

          default: err = (char*)dpErr2;
        }

The very last end bracket is for the main SWITCH(). Again, just curious.

Try it and see (you don’t need to try it on a core, you can just compile that anywhere you have access to a C compiler.)

In general, if a compiler accepts it, the code will run without housekeeping errors.

Whether the result is reliable, maintainable, or understandable is a different matter.

1 Like

I’d say the break is translared at compile time into a forward near jump and is not flow dependend.
Once instruction pointer reaches the respective machine code instruction it just jumps to its destination no matter where it came from (unlike a conditional branch instruction).

I really don’t know, to be honest, but I bet it exits the second (nested) switch statement.

But, why? Why do this to yourself? Perhaps you are curious about how C handles these convoluted situations, but then this isn’t the forum for that kind of question. Just put the “WriteEEByte” call up where your goto is, get rid of the label and the goto and make the code simpler and easier to read.

3 Likes

That’s what I ended up doing anyway for other reasons.

Why do that to myself? I understand that PCs have (practically) infinite resources, and you expect the compiler to handle every situation you could think of. (Oh, out of memory? Just go buy some more.) However, we don’t have that luxury on embedded systems…I was just curious about how much I could get away with on the Core. With a background in assembler, I know that doing such things are really risky (if the loop is PUSHed on the stack, for example).

Understood @WebDust21, however I will caution against premature optimization. GCC is really a fantastic compiler with all sorts of code shrinking tricks in it. You may be doing more harm than good to the size of your code by creating convoluted code paths that defeat its optimization heuristics. You can experiment with this both ways and look at the size of the code that is produced and see which is smaller.

2 Likes