Search Üner.com

Looking for something specific? Try the keyword search below (no query syntax or booleans allowed).

 

Most popular pages

Words for the wise

Random Oblique Strategy:

Random Turkish proverb:


Reversal Series: C/C++ goto

This time, we'll start with gotos. We all know them, and most of us hate them. If you're my age, you hate them because the line numbers on the left side of your vision are burned into your retina, along with the Atari or Amiga logo. And your fingertips are just a touch darker at the tips from those thermal prints that rubbed off so badly. If you're younger, you probably hate them because you were taught by some misguided soul that gotos don't belong in structured languages. I used to feel the same. But used properly, they can simplify debugging and improve readability. Let's look at a C program, and start with a really bad example:
loops = 2;
if (x)
{
   printf("Error code %d\n",x);
   return(x);
}
else
{
   x = some_other_func(x);
   if (x)
   {
      printf("Error code %d\n",x);
      return(x);
   }
   else
   {
      loops:
      x = still_another_func(x);
      if (x)
      {
         printf("Error code %d\n",x);
         return(__LINE__);
      }
      else
      {
         some_final_func(x);
      }
      if (x - 1 > 0)
      {
         goto loops;
      }
      now_something_else(x);
   }

   if (!x)
   {
      returnValue = 0;
   }
}
return(x);
So the first trouble is the readability. This is just a mess, and it's based on some code that I've seen in the wild. I know there's about a 50/50 split of you readers right now, with half thinking "OMG this is awful," and the other half thinking that it's fine. Well, it's not fine. It's not even close to fine. First off, we have serious readability issues. If these checks and conditions are nested much further, we'd be off the page and scrolling horizontally to figure out what's going on. And then there is the repetition in the error output. If you decide that later you want to call another error function rather than just printf, you have some work to do. Granted, it's search and replace, but in many cases it is not so simple. Next there's the weird loop: label. While this is a legitimate use of labels and gotos, it's not standard practice. Using them in this way is a bit like using the handle on your screwdriver to drive a nail. It'll work, but we make hammers for good reason. So let's clean it up, still using gotos, to see if we can make it more readable. We'll even us a macro for extra fun, just so we do something else most of you loath:
#define _abort(x) {\
   error_f("Internal error %s(%d) code=%d",__FILE__,__LINE__,x);\
   goto abort;}

if (x) _abort(x);

if ((x = some_other_func(x)) != 0) _abort(x);

while(true)
{
   if ((x = still_another_func(x)) !=0) _abort(x);
   some_final_func(x);
   if ((x - 1) <= 0) break;
   now_something_else(x);
}

abort:
return(x);
Now, besides this being (subjectively) easier to read, think of all the problems we solved. We need fewer break points to determine which line the module is failing on (actually, we need just one). By removing the short circuit returns, we leave open the possibility for a breakpoint at the end of the function, where we can inspect what value was chosen for __LINE__. We can now also change how we handle errors in one place - the macro. Say, for example, that we decide these error messages may reach customers, who may not need to see out file and line numbers, and all we really care about is the value of x. We could eliminate the macro, and change the code to just use a label. for example:
if (x) goto abort:
.
.
.
finish:
return(x);

abort:
error_f("Internal error %d",x);
goto finish;
Note how we jump past the "end" of the function, have some error fun, then jump back up. This allows us to still have on breakpoint and one point of exit. There are problems with jumps like this using labels, particularly scoping issues when you jump past declarations. But I'll show you how to deal with that next time in my next reversal "Where Variables Should Be Declared in C++"