int comparition

I am testing reliability of I2C but found a strange behavior. I tried:

int mistakes = 0, tries;
for (tries=0; tries>=0; tries++) {
  if (recievedValue()!=expectedValue) {
    mistakes ++;
  }
  Serial.println(tries);
}

and then

int mistakes = 0, tries;
for (tries=0; tries>(-1); tries++) {
  if (recievedValue()!=expectedValue) {
    mistakes ++;
  }
  Serial.println(tries);
}

Both codes end in an endless loop with tries overflowing to negative values as can be seen in serial output. How is this possible?

for loop comparison has to be false for the loop to exit.

  for (int i = 0 ; i < 10 ; i++)
  {
  }

is the standard way to loop 10 times, for instance. Normally you would declare the variable in
the loop statement itself, not previously in an outer block.

He says the loop is infinite though, even after tries wraps around to negative numbers, if I'm reading his post correctly. At that point, tries should no longer be greater than -1.

Hmm...this is honestly weird.

On IAR Workbench, this code works as we'd expect:

  INT16U mistakes = 0;
  INT16S tries;
  printf("%d\r\n", sizeof(tries));
  for (tries = 0; tries >= 0; tries++) {
     if (recievedValue() != expectedValue) {
        mistakes ++;
     }
     printf("%d\r\n", tries);
  }

On Arduino 1.6.8, this code loops forever as you're seeing:

  int mistakes = 0;
  int tries;
  Serial.println(sizeof(tries));
  for (tries = 0; tries >= 0; tries++) {
     if (recievedValue() != expectedValue) {
        mistakes ++;
     }
     Serial.println(tries);
  }

However, also on Arduino 1.6.8, this code does what I'd expect:

  int mistakes = 0;
  int tries;
  Serial.println(sizeof(tries));
  for (tries = 0x7ffe; tries >= 0; tries++) {
     if (recievedValue() != expectedValue) {
        mistakes ++;
     }
     Serial.println(tries);
  }

You may have found an honest to goodness compiler bug here. Here is the disassembly for the loop that runs infinitely:

  e0:  c0 e0        ldi r28, 0x00      ; Loads 0 into tries (r28/r29)
  e2:  d0 e0        ldi r29, 0x00
  e4:  4a e0        ldi r20, 0x0A
  e6:  50 e0        ldi r21, 0x00
  e8:  be 01        movw    r22, r28       ; Moves tries in prepartion for the Serial.println()
  ea:  82 e2        ldi r24, 0x22
  ec:  91 e0        ldi r25, 0x01
  ee:  0e 94 52 03     call    0x6a4          ; Calls Serial.println()
  f2:  21 96        adiw    r28, 0x01      ; Adds 1 to tries
  f4:  f7 cf        rjmp    .-18           ; Unconditionally jumps back to loop?  No exit condition?

No wonder this loops forever. There isn't even a way for it for it to break out of the loop.

Yet if I init tries to 0x7ffe, the compiler doesn't even bother making a loop at all:

  e0:  4a e0        ldi r20, 0x0A   
  e2:  50 e0        ldi r21, 0x00   
  e4:  6e ef        ldi r22, 0xFE    ; Loads 0x7ffe into tries (r22/r23)
  e6:  7f e7        ldi r23, 0x7F
  e8:  82 e2        ldi r24, 0x22
  ea:  91 e0        ldi r25, 0x01
  ec:  0e 94 57 03     call    0x6ae        ; Calls Serial.println()
  f0:  4a e0        ldi r20, 0x0A
  f2:  50 e0        ldi r21, 0x00
  f4:  6f ef        ldi r22, 0xFF    ; Loads 0x7fff into tries (r22/r23)
  f6:  7f e7        ldi r23, 0x7F
  f8:  82 e2        ldi r24, 0x22
  fa:  91 e0        ldi r25, 0x01
  fc:  0e 94 57 03     call    0x6ae        ; Calls Serial.println()

I'd say "smart compiler" in the 0x7ffe case because not all of them recognize the optimization of unrolling loops of 2. I have to say "dumb compiler," however, as if it knows that the loop should end in the 0x7ffe case then it should know that it should end in the 0 case too. Your initial value should have absolutely nothing to do with your end condition.

So...who do compiler bugs get reported to?

Edited to Add:

Alright, this is even more bizarre. This is what the 0x7ffe case looks like in Arduino 1.0.4, where it also never exits the "loop" that it had unrolled:

  e0:  6e ef        ldi r22, 0xFE
  e2:  7f e7        ldi r23, 0x7F
  e4:  88 e9        ldi r24, 0x98
  e6:  91 e0        ldi r25, 0x01
  e8:  4a e0        ldi r20, 0x0A
  ea:  50 e0        ldi r21, 0x00
  ec:  0e 94 d1 03     call    0x7a2
  f0:  6f ef        ldi r22, 0xFF
  f2:  7f e7        ldi r23, 0x7F
  f4:  f7 cf        rjmp    .-18

Looks like the compiler is broken to me. If I'm missing something dumb, someone correct me please.

Thanks for help, I think I have found the reason: it’s undefined behavior. It looks like in C integers are not allowed to overflow and the compiler expects the user to ensure it. It is quite strange for me - it is optimalization in some strange sense but I guess it introduces many bugs and saves little code - generating mostly bugs by removing “unused code” as in my case.

volatile int tries;
int mistakes = 0;
for (tries=0; tries>=0; tries++) {
  if (recievedValue()!=expectedValue) {
    mistakes ++;
  }
  Serial.println(tries);
}

works as expected (at least by me).

Hmm. I wasn't aware compiler's were able to do whatever they wanted for signed integer overflows. Thanks for teaching me something today.

It still seems broken, however, for it to do different things when you have a different starting condition(0 vs 0x7ffe). Undefined behavior tends to mean undefined from compiler to compiler...not undefined within the same compiler!

You know...I did have optimization turned off when I tried using IAR Workbench. I wonder if it would loop forever if I turned it on.

BigBobby: It still seems broken, however, for it to do different things when you have a different starting condition(0 vs 0x7ffe). Undefined behavior tends to mean undefined from compiler to compiler...not undefined within the same compiler!

You know...I did have optimization turned off when I tried using IAR Workbench. I wonder if it would loop forever if I turned it on.

It follows nearly exactly the Wiki example (it wasn't devised to reproduce it, I swear - I just remembered I have read something like that not so long ago - just lucky). If the starting value is negative the compiler sees it doesn't meet the for condition and removes the whole loop. If it is 0 at start the compiler thinks it can never become negative so it removes the condition check and makes infinite loop. I guess it would work if the initial value were unknown at time of compilation even without the volatile keyword:

for (int i = Serial.parseInt(); i>=0; i++) {...}

wouldn't end in infinite loop regardless the starting condition.

But in Arduino v1.6.8 it didn’t enter an infinite loop when started at one positive number (0x7ffe), but it did enter an infinite loop with another (0). That seems inconsistent.

Oh I didn't realize 0x7ffe is positive near overflow - I wrongly assumed it's negative just after overflow :-)

You know, I'm familiar with some top engineering companies that prohibit their FW engineers from using the optimizer for this reason? They basically spend extra money on their micros so that they don't run into these sorts of problems.

Optimizers can really punish you if you don't code perfectly, often in ways that are tough to catch. Also, while compiler bugs are rare, when they are found they're usually in the optimizer.