Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C - Why is a for loop getting stuck when using a uint64_t counter, whereas a while loop isn't?

When I use a for loop with a uint64_t as a counter, it gets stuck forever, even though the condition seems to be well defined.

Offending MCVE

#include <stdio.h>
#include <inttypes.h>

int main() {
    uint64_t i;
    for (i = 15; i >= 0; i--) { printf("%"PRIu64" ", i); }
    return 0;
}

Partial output

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 18446744073709551615 18446744073709551614 18446744073709551613 18446744073709551612 18446744073709551611 18446744073709551610 18446744073709551609 18446744073709551608 18446744073709551607 18446744073709551606 18446744073709551605 18446744073709551604 18446744073709551603 18446744073709551602 18446744073709551601 18446744073709551600 18446744073709551599 18446744073709551598 18446744073709551597 18446744073709551596 18446744073709551595 18446744073709551594 18446744073709551593 18446744073709551592 18446744073709551591 18446744073709551590 18446744073709551589 18446744073709551588 18446744073709551587 18446744073709551586 18446744073709551585 18446744073709551584 18446744073709551583 18446744073709551582 18446744073709551581 18446744073709551580 18446744073709551579 18446744073709551578 18446744073709551577 18446744073709551576 18446744073709551575 18446744073709551574 18446744073709551573 18446744073709551572 18446744073709551571 18446744073709551570

It seems it's ignoring the stop condition, and so it rolls over.

However, when changing it to an "equivalent" while loop, everything works fine:

Correct MCVE

#include <stdio.h>
#include <inttypes.h>

int main() {
    uint64_t i = 16;
    while (i--) { printf("%"PRIu64" ", i); }
    return 0;
}

Complete output

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 

Am I missing something regarding the use of uint64_t counters in a for loop? Any help is greatly appreciated!

like image 898
logo_writer Avatar asked Jan 07 '23 14:01

logo_writer


1 Answers

The condition i >= 0 is alwaays true if i is an unsigned type. Decrementing an unsigned zero will not produce a negative value, but the counter will wrap to the maximum representable number for the given type.

C represents ranges with an inclusive lower bound and an exclusive upper bound. For example, an array of N elements has indices 0 through N - 1. The upper bound itself isn't a valid array index.

This convention means that you use a value before incrementing it, but deceremt it before using it. Consider a simple stack:

    stack[nstack++] = x;      // push a value
    x = stack[--nstack];      // pop a value

The same logic goes for loops: When you move forwards, use the value before you increment it:

    for (var i = 0; i < N; i++) { use(i); }

When you move backwards, decrement first and then use it:

    for (var i = N; i-- > 0; ) { use(i); }

This loop is equivalent to your while. The update section, which happens after processing the body, is empty here. The check is performed on the value before entering the loop; the loop body has the updated value.

This backwards loop might look awkward with the empty update section, but in other ways it is orthogonal to the forward version:

  • It uses the actual bounds; there's no need to start with N - 1;
  • It works equally well for arbitrary bounds as long as they follow the convention of inclusive lower and exclusive upper bound;
  • The test is a pure inequality, not a greater/less-then-or equal comparison.
like image 106
M Oehm Avatar answered Jan 28 '23 11:01

M Oehm