Monday, June 19, 2017

Go To Statement Considered Helpful (part 2: Nested Loops)

If you're in a nested loop in C code, and you want to issue a break or continue statement, but want it to apply to the outer loop, you're going to have some awkward code.

  for(j=0;i<j_end;++j)
  {
      for(i=0;i<end;++i)
      {
          ...
          if (condition) next_j; // break then continue?
          ...
      }
      ...
  }

Implementing that 'next_j' statement could be done with setting a flag, issuing a break, and then checking the flag after the inner loop terminates.  What we want is a "goto continue_outer_loop." And when you do this, it can be a perfect place for one of my favorite tricks, the use of "if (0)" for live code. At the end of the outer loop, put in the block of code, "if (0) { continue_outer_loop: continue; }" and you can then use your goto to implement the feature that the language is missing.  This looks like:

  for(j=0;i<j_end;++j)
  {
      for(i=0;i<end;++i)
      {
          ...
          if (condition) goto continue_outter_loop;
          ...
      }
      ...
      if(0) continue_outter_loop: continue;
  }

A nice thing to do would be to imagine how the language might be extended to eliminate this need for a goto. Loops could easily be named, allowing the name of the loop to be an optional parameter to a break or continue statement. What if instead of for(i=0;i<10;++i), you could write for(i=0;i<10;++i;index_loop). Then you could use "break index_loop;" inside an inner loop with no need for a goto. Good luck getting the language committees to agree to this anytime soon. On the bright side, Java recognized this shortcoming, so the language provides for named loops; in C and C++, goto lets you implement the missing feature.

In this case, all this can be included in macros, as shown below. The first pass at trying this can result in compiler warnings about unused labels, which is why I added the extra do-nothing gotos. With even the most minimal compiler optimizations, this should provide exactly the same execution as if the language provided for named loops natively. The only restriction is that you can't use the same name for more than one loop in the same function.


/*
 * Named loops:
 *
 * Name any loop, typically the first thing after the opening brace.
 * Then in a nested loop, break or continue from it with
 * BREAK_LOOP/CONTINUE_LOOP.
 *
 * Code Copyright 2015 by Preston Crow
 * used by permission
 * (You have permission to use this as long as you keep
 *  this comment block intact.)
 */
#define NAME_THIS_LOOP(_name)            \
if(0)                                    \
{                                        \
    goto _continue_loop_ ## _name;       \
    _continue_loop_ ## _name : continue; \
}                                        \
if(0)                                    \
{                                        \
    goto _break_loop_ ## _name;          \
    _break_loop_ ## _name : break;       \
}                                        \
do { ; } while (0) /* Force a semicolon */
#define CONTINUE_LOOP(_name) goto _continue_loop_ ## _name
#define BREAK_LOOP(_name) goto _break_loop_ ## _name


Note that the naming macro can go anywhere in the loop where a continue or break statement would work as expected. Using those macros, the code is quite readable:

  for(j=0;i<j_end;++j)
  {
      NAME_THIS_LOOP(outter_loop)
      for(i=0;i<end;++i)
      {
          ...
          if (condition) CONTINUE_LOOP(outter_loop);
          ...
      }
      ...
  }


I thought I was very clever the day I came up with that. I felt a little less clever when researching revealed that others have also suggested very similar macros for the same purpose. In any case, I highly recommend that you use these macros or the like in all C and C++ projects where appropriate.

No comments:

Post a Comment