Friday, March 23, 2012

C/C++ loop surprises

A basic element of almost any program is a loop. Loops are a simple concept to grasp, but they can have pitfalls in some languages; here I will present one I learned about yesterday in C.
The basic for loop in C is of this form:
int n;
unsigned int i;
for (i=0; i<n; i++) {
    //do something
}
Which is pretty foolproof as long as you leave i alone inside the loop. But another type of loop is the decrementing loop. Here is a simple program that prints the numbers from 5 down to 1:

#include <stdio.h>


int main (int argc, char **argv) {
int n = 5;
unsigned int i;
for (i=n; i>=1; i--) {
printf("%u\n", i);
}
}
And the output:

5
4
3
2
1

Ok. Now what if we want to count down to 0? The obvious thing to do would be to change line 5 to:
for (i=n; i>=0; i--) {
But when this is compiled and run, it doesn't stop:
5
4
3
2
1
0
4294967295
4294967294
4294967293
4294967292
4294967291
4294967290
4294967289
4294967288
4294967287
4294967286
...
What happened? Well, the way a loop works is this: in each iteration, the condition is tested, then (if true) the loop body is executed, then the iterator is executed. So at what should be the last cycle, i is compared to see if it is greater or equal to zero (it is), then the body (printf) is executed, then i is decremented - and because i is an unsigned int, it wraps around to its maximum value! The next time around, it is compared against 0 (it is still greater), and the cycle continues indefinitely.
So, how do we make a loop that counts down to 0? The simplest way is to replace
unsigned int i;
with
int i;
This removes the sign, and after passing 0 i will go negative for the loop's termination.
But this brings two disadvantages: one, that although it will still compile, in some loops using a signed int for the loop variable will generate a compiler warning; and two, that we are creating a loop that should only use positive numbers anyway, so making it signed for a sign that is never used within the loop is a bit superfluous.
One potential solution is to move the decrement to the top of the loop, and test its value before being decremented:
for (i=n; i-->0; ) {
Here, we are taking advantage of the fact that the decrement operator returns the value pre-decrement. We then leave the iterator field empty. This prints:
4
3
2
1
0
But wait! It started at 4! This is because it gets decremented in every cycle - including the first. So what we actually have to do is to add one in the first loop, giving us the somewhat ugly, but working:
for (i=n+1; i-->0; ) {
Another solution is to check that i+1 is greater than 0. This seems like it would get confused when i wraps around to UINT_MAX, but that wrap actually helps, because UINT_MAX+1 is 0.
for (i=n; i+1>0; i--) {
Finally, there's one more option, which is to not use a for loop. Using while loops is generally regarded as bad practice because they can lead to infinite loops, however as we have seen this particular test may be infinite under a for loop anyway. A while loops gives us the flexibility to put the test and the iterator where we choose, so we can put the test after the body but the iterator after the test. Here's a solution as a while-break loop:
#include <stdio.h>


int main (int argc, char **argv) {
int n = 5;
unsigned int i=n;
while (1) {
printf("%u\n", i);
if (i==0) {
break;
}
i--;
}
}

No comments:

Post a Comment