While statement considered harmful?

Christopher Bazley, July 2012 (minor revisions for Medium, September 2022)

Lately I have been editing an otherwise well-structured and intelligible program written by a C programmer who habitually iterates over linked lists using the following idiom:

int print_shopping_list( listitem_t *list, int max_price ) {
  int found_count = 0;
  listitem_t *item = list;
  while (item != NULL) {
    if ( item->price <= max_price ) {
      printf( "Buy %s priced at %d pence", item->name, item->price );
      found_count++;
    }
    item = item->next;
  }
  return found_count;
}

Superficially, there is nothing wrong with this. However, consider what happens if another programmer (with a nut allergy) adapts the program to reject peanuts:

int print_shopping_list( listitem_t *list, int max_price ) {
  int found_count = 0;
  listitem_t *item = list;
  while (item != NULL) {
    if ( item->type == ITEMTYPE_PEANUTS ) {
      continue; /* No peanuts because of my allergy */
    }
    if ( item->price <= max_price ) {
      printf( "Buy %s priced at %d pence", item->name, item->price );
      found_count++;
    }
    item = item->next;
  }
  return found_count;
}

Oh dear! The modified program will never terminate if it encounters peanuts in the shopping list, because continue jumps to the next loop iteration without assigning a new value to the current item pointer.

This error could have been averted, had the original programmer used a for statement instead of while:

int print_shopping_list( listitem_t *list, int max_price ) {
  int found_count = 0;
  for ( listitem_t *item = list;
        item != NULL;
        item = item->next ) {
    if ( item->type == ITEMTYPE_PEANUTS ) {
      continue; /* No peanuts because of my allergy */
    }
    if ( item->price <= max_price ) {
      printf( "Buy %s priced at %d pence", item->name, item->price );
      found_count++;
    }
  }
  return found_count;
}

The above code works because the third expression of a for statement (to advance to the next iteration) is executed after a continue statement.

Any loop in the following form:

for( a; b; c ) {
  d;
}

can be rewritten as multiple statements:

a;
while( b ) {
  d;
  c;
}

Most modern programming languages offer continue, for and while statements. In my view the while statement should never be used for loops that require separate expressions to test for termination and to advance to the next iteration.

In a while loop, the next-iteration expression is too easily omitted or not executed if it is in a separate statement marooned at the end of the loop's body (which may be many lines distant).

Using for instead of while makes programs more concise, more robust, and easier to read because all of the expressions relating to a loop are on the same line (or a few consecutive lines).