6

So I'm using size_t instead of int in any indexing for loop to prevent negative indices. But when counting down, this leads to an overflow:

for (size_t i = 10; i >= 0; --i) {
    // Do something, f.ex. array[i] = i
}

What would be a nice way to prevent this?

  • Using int instead?
  • Using i == 0 as the termination condition? (This would however not work if I need the 0)

I'd appreciate any feedback!

AdHominem
  • 1,204
  • 3
  • 13
  • 32
  • 1
    It is not technically an overflow because `size_t` is an unsigned type, but it is definitely an infinite loop since the termination condition is always true. – chqrlie Dec 10 '16 at 13:21

6 Answers6

7
for (size_t i = 10; i <= 10; --i) // do something

When overflow do happens, it will round to the largest integer and thus the condition will fail.

Yan Zhou
  • 2,709
  • 2
  • 22
  • 37
6
for (size_t i = 11; i-- > 0; ) {
    // Do something, f.ex. array[i] = i
}

Note: The question starts the loop with value=10(which is strange, but not impossible). I start with 11, but the first time the loop body is enterered, it has already been decremented to 10.

wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • Yes. The quetion starts the loop with value=10(which is strange, but not impossible). I start with 11, but the first time the loop *body* is enterered, it has already been decremented, – wildplasser Dec 10 '16 at 13:22
5

It is not technically an overflow because size_t is an unsigned type, but it is definitely an infinite loop since the termination condition is always true.

Unsigned integers wrap around when decremented at 0. Note that your loop will run 11 times before the wrap around occurs, not 10.

You must check for the condition before decrementing the index. Starting the enumeration with an initial value one more than the maximum valid index improves visual consistency and simplifies the test.

Here is a corrected version where you can see that the initial value for i is the number of elements of the array:

int array[11];
for (size_t i = 11; i-- > 0; ) {
    // Do something, f.ex. array[i] = i
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
3

The idiomatic, though not to everyone's taste way, is to use the slide operator:

for (size_t i = 10 + 1; i--> 0; )

It isn't really an operator but that's what it has become known as over the years.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
3

size_t i = 10; i >= 0; is never false as size_t is some unsigned type and all values are greater than or equal to zero.

... size_t which is the unsigned integer type of the result of the sizeof operator; ...
C11 §7.19 2

Enable all warnings

A good compiler with warnings enabled would have warned about this.
Hopefully, that infinite loop would never had occurred as an investigation to the warning would have first rectified the problem.


Best alternative depends on coding goals

Good code avoids magic numbers like this naked 10. Better if code derived that. In this simple case, it should have been 11.

#define A_SIZE 11
int array[A_SIZE];
...
for (size_t i = A_SIZE; i-- > 0; ) {
    // Do something, f.ex. array[i] = i
}

OTOH, code may have had break conditions in the loop and needs i in later code to indicate array[] usages

size_t i = A_SIZE;
while (i > 0) {
  if (...) break; 
  i--;
  // Do something, f.ex. array[i] = i
  if (...) break; 
}
// Do something with i

Code may have a contract requirement to use a 10 in various places.

// Contract says loop must handle indexes 0 to N, inclusive
#define N 10
int array[N + 1];

for (size_t i = N; i + 1 > 0; i--) {
  // Do something, f.ex. array[i] = i
}

Good optimizing compilers will not perform a +1 on each i + 1 > 0, but create equivalent efficient code.

Code is a fashion that best conveys the overall meaning of the code.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    Amazing! I never thought of `i + 1 > 0` to test for wrap around. It is equivalent to `i != SIZE_MAX`, less ugly but arguably more intriguing as it begs for simplification. One case is not covered: it does not work for `N = SIZE_MAX`, which is rather rare on modern systems, but was not uncommon in the days of 16-bit protected mode. You could also use `i != -1`, which has the same shortcoming. – chqrlie Dec 10 '16 at 18:24
  • Looks nice indeed. I guess in practice this would be error prone to off by one's since you could easily forget to add 1. Using SIZE_MAX is not that much more counterintuitive than `i <= n; --i` or `i-- > 0;`, all of them anticipate the wrap around. – AdHominem Dec 13 '16 at 06:44
2

A simplest way is to increase the upper value. For example

const size_t N = 10;

for (size_t i = N + 1; i != 0; --i) {
    // Do something, f.ex. array[i-1] = i-1
}

or

const size_t N = 10;

for (size_t i = N + 1; i-- != 0; ) {
    // Do something, f.ex. array[i] = i
}

In general case when i can be equal to the maximum value stored in an object of the type size_t you can use the following trick

#include <stdio.h>

int main( void )
{
    const size_t N = 10;

    for (size_t i = N, j = N; !( i == 0 && j == -1 ); j--)
    {
        i = j;
        printf( "%zu ", i );
    }

    printf( "\n" );
}

Otherwise you can use do-while loop. It is more suitable in this case. For example

size_t i = N;

do
{
    printf( "%zu ", i );
} while ( i-- != 0 );
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335