Counting up vs. counting down
tedunangst.com
tedunangst.com
for (size_t i = numBurritos - 1; 0 <= i && i < numBurritos; i--)
But this loops infinitely if sizeof(numBurritos) == sizeof(size_t) where size_t is unsigned, because the entire space of possible values returns True. This failure mode exists whether we increment or decrement over unsigned values.The root problem here isn't decrementing. The root problem is the unsigned value. So maybe it's best practice to use signed values whenever possible. This way, the existence of negative values guarantees the existence of a sentinel.
for (size_t i = numBurritos - 1; i >= 0; i++)
if (wantBurrito(i))
break;
Apart from the didactic bug that i >= 0 is always true for size_t, this is supposed to count down, so i++ should be replaced with i--: for (size_t i = numBurritos - 1; i >= 0; i--)
if (wantBurrito(i))
break; for (size_t i = numBurritos; i-- > 0;)
if (wantBurrito(i))
break;
Where an advantage is that many assembly languages have a "jump if zero" or "jump if not zero" instruction.EDIT: It appears the article has been updated to reflect this method.
size_t i = num_burritos;
while (i --> 0) // Read: while 'i' goes to zero
if (want_burrito(i))
break;
While it sounds nice, I don't think it's a good idea.Clearly the correct solution is to use a better language, e.g.:
return next((burrito for burrito in reversed(burritos) if burrito.wanted()), None)Fun fact: I usually count down unless there's a code-specific reason to count up. It's one of the biggest "tells" that I wrote a piece of code, and it exists because I'm old enough to have worked with compilers stupid enough that they really did produce more efficient code that way. Nowadays I wouldn't generally recommend it, because AFAIK nobody has made a production-level compiler that dumb for quite a while.
i = arraylength;
while (i-- > 0)
In other cases it really does make more sense to count up. Another unconsidered possibility is that the "found" case might actually return from an iterator function, so you can put the "not found" case after the loop regardless of which way you're counting.The "counting down can cause an infinite loop" problem does exist, but I've also seen cases where counting up can fail too (e.g. index incremented twice within a loop either accidentally or deliberately), or where confusion of whether to use "i" or "i-1" caused bugs. It's probably even easier for a static analyzer to catch a counting-down bug than a counting-up bug, so I'm not really sure that a preference for counting up is all that helpful.
There's an argument to be made that this check would hurt performance (and I buy that argument--given C's goals and the time when it was made, the type system compromises it made make sense). But it would be nice if processor manufacturers made add/sub instructions that detected wraparound, which would allow languages to implement such checks performantly.
for (size_t i = numBurritos - 1; i != -1; i--)
This will fail on a platform where sizeof(size_t) < sizeof(int). I admit that I don't know of any such platform, but I can't see any reason why it wouldn't be possible.A better way to write this is
for (size_t i = numBurritos - 1; i < numBurritos; i--)>> Especially consider the case of an index into a string (char *) on a 64 bit platform. size_t is the appropriate type for an index because the limited range of int is not nearly enough.
That's just bull in most applications. Even if you could run 1 trillion iterations per second, the loop would take 2^23 seconds to complete or about 97 days. In general if you're not doing "big data" and int will index an array just fine thank you. And that's indexing bytes, it should index any other type just fine in any case - assuming of course that a pointer and int are the same size.
for (size_t i = n; i > 0; ) {
i--;
...
}
I'd have gone with "i-- > 0" if I wasn't feeling out the consequences of trying to avoid all integer overflow, though.This probably demonstrates the point where clever code is more likely to have bugs ...
I, too, fail to understand how this is clever. Can someone enlighten me?
Whether that's a good thing is up for debate.
C's variable promotion system is invisible and silent and in my experience a frequent source of bugs in exactly this case (tests in for loops), which is why there's enough meat here for a blog post. It's usually easy to avoid the promotion issue by using the same type for the loop variable and its test condition. In cases like this, sometimes it's better to just use a straightforward and obvious while() loop instead of a correct but tortured for() loop. That makes the relationship between the loop variable updates and the loop body controllable and explicit.
Conceptually, it works through promotion. -1 is cast to unsigned/size_t before the test, which then succeeds. At the assembly level, a 32-bit -1 is 0xFFFFFFFF, which is also the result of the underflow of i, so it works at that level, too.
At any rate, it worked in the compiler I tried it with.
There are iterators in C++, but not in C.
You can create iterators in any language, and I was referring to the abstract concept of iterators, not the particular form of iterators found in C++'s standard library.