That's a really funny example, given how many bugs have been found in C programs because idiomatic loops are wrong in the edge cases.

How do you idiomatically write a loop to iterate over signed ints from i to j (inclusive) in increasing order, given i <= j?

What does that loop do when j is INT_MAX?

I can imagine someone who sketches out little proofs in their head - or even on paper - missing that case too. It’s easy to forget you’re not doing normal arithmetic when doing arithmetic in C!

Yeah, it's a hard case in general. But C's idioms really don't encourage you to think about it. You really need to default to a loop structure that checks the termination condition after the loop body but before the increment operation for inclusive end coordinates.

It's easy to think that's what do/while is for, but it turns out to be really hard to do the increment operation after the conditional, in general. What you really want is a loop structure with the conditional in the middle, and the only general purpose tool you get for that is a break. C (or any language with similar syntax) really doesn't have an idiom for doing this correctly.

This may be hubris, but…

  int i = start;
  do thing_with(i) while (i++ <= end);

I did consider that, but I wrote "in general" for a reason. It works very specifically in the case of "add one" or "subtract one", but it doesn't work with anything more complicated, like chasing pointers or adding/subtracting more than one at a time.

You could write functions to do the update and return the old value so you could use them in the same way, but I don't like this either. This is mostly because it orders the termination check and the update logic the wrong way around. If there's IO involved in checking for the next thing, for example, side effects of that unnecessary operation might interfere with other code.

You could resolve that by moving the termination check into the update logic as well, but now you're seriously complecting what should be independent operations. I don't think the tradeoff is there versus just using a break. But mostly, this is a self-inflicted problem in C's language constructs and idioms. I just don't have this problem in many other languages, because they provide end-inclusive looping constructs.

> I did consider that, but I wrote "in general" for a reason. It works very specifically in the case of "add one" or "subtract one", but it doesn't work with anything more complicated, like chasing pointers or adding/subtracting more than one at a time.

You're reminding me of the book "Modern Compiler Design." The author goes over how to compile a general Pascal-style for-loop correctly, accounting for increasing or decreasing ranges, differing step sizes, and accounting for overflow. It was written using just goto statements, so I adapted a version of it to C. Just replace "intN_t" with an actual integer size. It works by calculating the number of times the loop will run. If "from" is equal to "to," it's still going to run at least once. Again, this is not mine, just adapted from the author's code (Dick Grune's).

  // enumerate: print out numbers from "from" to "to", inclusive, with step size "by"
  void enumerate(intN_t from, intN_t to, intN_t by) {
      uintN_t loop_count;
      intN_t i;
      if (by > 0) {
          if (from > to) return;
          loop_count = (to - from) / by + 1;
      } else if (by < 0) {
          if (from < to) return;
          loop_count = (from - to) / -by + 1;
      } else /* (by == 0) */ {
          // Maybe some kind of error
          return;
      }
      for (i = from; ; i += by) {
          printf("%d\n", i);
          if (--loop_count == 0) break;
      }
  }
You can see it's more complicated than the idiomatic C for-loop, haha. But that's just a general solution. Like you guys noted, it could be simpler for step sizes of 1.