Loop doesn't work consistently...

I feel kind of stupid, but here goes: The following code prints a single zero:

int x;

void setup() {
  Serial.begin(9600);
  x = 0;
}

void loop() {
  if (x == 0) {
    Serial.print(x);
    x++;
  }
}

Why does the following code keeps printing zeros forever? Why doesn't "x" increase to end the printing?

int x;

void setup() {
  Serial.begin(9600);
  x = 0;
}

void loop() {
  if (x == 0) {
    Serial.print(x);
  }
  x++;
}
 if (x == 0) {
    Serial.print(x);
    x++;
  }

If x equals zero then print it and increment its value so that next time the test occurs it fails and its value never changes again

 if (x == 0) {
    Serial.print(x);
  }
  x++;

If x equals zero then print it. Increment x whether its value was printed or not so that next time it equals zero it will be printed again (and again, and again)

UKHeliBob: if (x == 0) {     Serial.print(x);     x++;   }

If x equals zero then print it and increment its value so that next time the test occurs it fails and its value never changes again

 if (x == 0) {
    Serial.print(x);
  }
  x++;

If x equals zero then print it. Increment x whether its value was printed or not so that next time it equals zero it will be printed again (and again, and again)

I'm sorry, I still feel stupid. In the 2nd code, "x" will also become "1" in the first execution of the loop. Why does "x" gets printed in the 2nd execution, since "x" is now "1"?

It doesn't get printed on the 2nd loop. But after 65536 iterations of loop, x will again be zero. You just don't notice the delay while it gets there because the arduino is so fast.

Edit: missing don't.

With integer maths when the value of a variable reaches its upper limit it rolls over to 0. And this happens incredibly quickly. As your variable X is declared as an int it will increment 1,2,3 ...32767, -32768, -32767, -32766 ... -1, 0, 1, 2 ,3 etc.

Put the line delay(50); as the last thing in loop() and see the difference. Or just print every value of X

...R

I think the problem is that you're being tricked by the speed of the processor. It is printing when x=0, yes, but x is being incremented very, very quickly and when it rolls over to 0, a '0' is printed again. The processor is fast enough to make it appear like it's printing zeros every pass when in fact it's doing so after many thousands of passes through loop().

Make x a long instead of an int and see how rapidly 0s get printed then.

Ah, you are all so right! I completely forgot about the speed of execution and the rolling over to 0!!! Thank you! :)

You declared your variable with signed integer type. In C++ signed integer overflow results in undefined behavior, not in some "rollover". Modern compilers can take this into account, e.g. optimize the code on the assumption that signed arithmetic never overflows. It is called strict overflow semantics. The result is code that behaves in "unexpected" (by some users) way.

As far as C++ language is concerned, the output that you observe is exactly the consequence of that undefined behavior. No more, no less. There's no need to try to pick it apart further to see whether this is really a mere "rollover" or a peculiarity of strict overflow semantics.

If you need rollover behavior - use unsigned types, not signed ones.


Having said that, if this undefined behavior indeed proves to be a "rollover", then it should pass through 0 quite often, since on this platform int is a 16 bit type.

Montmorency: You declared your variable with signed integer type. In C++ signed integer overflow results in undefined behavior, not in some "rollover".

I am certainly not an C/C++ expert. Are you saying that the behaviour I outlined in Reply #4 is not guaranteed?

...R

Robin2: I am certainly not an C/C++ expert. Are you saying that the behaviour I outlined in Reply #4 is not guaranteed?

...R

Yes, that is exactly what I"m saying.

Again, I'm talking specifically about formal guarantees made by the language itself. In C and in C++ signed overflow does not roll over. It produces undefined behavior.

Specific implementations are free to extend the language spec, e.g. and define the undefined. They are free to guarantee the rollover in this case. I don't know though whether avr-gcc does it.

Strict overflow semantics is implemented by gcc for a reason: it opens the door for some valuable optimization opportunities. I would be surprized to see avr-gcc to abandon it in favor of "nice rollover" behavior.

Montmorency: Yes, that is exactly what I"m saying.

So, in the Arduino world, what might the alternative behaviour be?

Do you have a program I could try which demonstrates the problem?

...R

Robin2:
So, in the Arduino world, what might the alternative behaviour be?

Whatever the compiler decides to do if it detects the violation. It is hard to predict how the gears in the optimizing compiler’s brain will mesh for code with undefined behavior. We’ve seen all kinds of weird results.

Robin2:
Do you have a program I could try which demonstrates the problem?

For Arduino IDE specifically? No, I didn’t have a chance to try it in Arduino IDE and its avr-gcc. Otherwise, the Net is full of examples that can be found by doing a search for “gcc strict overflow semantics”.

A simplest example might look as follows

#include <stdio.h>

void foo()
{
  for(int i = 0; i > 0; ++i)
    printf("Hello World\n");
}

This loop is obviously supposed to iterate until i rolls over. However, compiled with AVR-GCC 4.6.4 the function becomes empty: Compiler Explorer (at all optimization levels above -O0). The compiler detected the undefined behavior and decided to simply discard the whole loop. The optimization (and other) gcc settings for Arduino IDE might be different and might produce different result.

I also have this interesting example in my notebook, which also relies on compiler at compile time detecting overflow in signed arithmetic and generating “weird” code

/* Cycle in main performs only one iteration with -O2 and higher */

#include <stdio.h>

int N = 2;
/* When `N` is a file-scope variable, the compiler remains silent and generates 
   only one iteration of the cycle (the code outputs `1`). If `N` is made local 
   or an explicit constant is used in the cycle condition, a warning about 
   undefined behavior on the second iteration is issued by the compiler, but it
   generates both iterations (the code outputs `2`) */

int main(void)
{
  int ni = 0;
  int arr[3];
 
  for (int i = 1; i <= N; i++)
  {
    ++ni;
    arr[i] = i * 1073741824; /* Triggers signed overflow when `i` is greater than 1 */
  }
 
  printf("%d\n", ni);
}

Would be interesting to reproduce this in Arduino IDE. Of course, the constants will have to be updated, considering that Arduino int is only 16 bits wide.

No, I didn't have a chance to try it in Arduino IDE and its avr-gcc.

Try it.

The OP is concerned about the actual performance of the Arduino hardware and the Arduino IDE, not the vagaries of the C/C++ programming language.

jremington: Try it.

For what purpose? Regardless of what the result might be, my points will still stand. This will still be regarded as "mandatory learning". Undefined behavior is undefined behavior, even if it would appear to "work fine" here and how.

jremington: The OP is concerned about the actual performance of the Arduino hardware and the Arduino IDE, not the vagaries of the C/C++ programming language.

That's incorrect. The OP's question is directly related to the properties of C++ language and its implementation used by Arduino IDE.

Firstly, the programming language we use to program Arduino hardware is C and C++ as implemented by avr-gcc compiler. And yes, specifically because of this, the OP's question is directly related to the aforementioned "vagaries of the C/C++ programming language".

Secondly, even if Arduino IDE used some bizarre disfigured implementation of C and C++ (which it thankfully does not), we in this forum would still strongly advise beginners not to deviate from the proper C and C++ without a really good reason. It is a matter of good programming practices this forum is also dedicated to.

This is something I'm not offering for discussion, really.

I have just compiled this code with Arduino’s avr-g++ command:

int x = 0;

extern void bar();
void foo()
{
    if (x+1 < x) {
        bar();
    }
    ++x;
}

Here’s the command to build it:

avr-g++ -O2 -mmcu=atmega328p -S 610243.cc -o 610243.s

And here’s the assembly code output:

        .file   "610243.cc"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
        .text
.global _Z3foov
        .type   _Z3foov, @function
_Z3foov:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
        lds r24,x
        lds r25,x+1
        adiw r24,1
        sts x+1,r25
        sts x,r24
        ret
        .size   _Z3foov, .-_Z3foov
.global x
        .section .bss
        .type   x, @object
        .size   x, 2
x:
        .zero   2
        .ident  "GCC: (GNU) 5.4.0"
.global __do_clear_bss

The foo() function only increments x and returns. It never calls bar() under any circumstances. This is because “x+1 < x” can be assumed to be always false, so the if statement and body can be removed by the optimizer.

ETA: If you add -Wall to the build command, you get this warning:

610243.cc: In function 'void foo()':
610243.cc:6:15: warning: assuming signed overflow does not occur when assuming that (X + c) < X is always false [-Wstrict-overflow]
     if (x+1 < x) {
               ^

I would pay attention to that warning since it’s extremely relevant.

This is also something I'm not offering for discussion.

Good to know what sort of person we are dealing with here.

christop:
The foo() function only increments x and returns. It never calls bar() under any circumstances. This is because “x+1 < x” can be assumed to be always false, so the if statement and body can be removed by the optimizer.

Very nice. Thank you for making this experiment. (I’m planning to play around with this myself this evening.)

This is a classic example of how strict overflow semantics works in GCC. I hope that Arduino IDE does not make any efforts to override this semantics and gives AVR-GCC full freedom to optimize the code using normal C++ rules.

When this was originally introduced (in GCC 4, if memory serves), it triggered quite an outcry from various “hackish” types who love their “rollovers”… But sorry, guys, these “rollovers” were never in the language in the first place. (A similar hysteria was triggered by strict aliasing semantics as well.)

“Want a rollover? Use an unsigned type” - that’s how it’s always been.

Grumpy_Mike: Good to know what sort of person we are dealing with here.

Yeah, exactly... "2+2 equals 4" kind of person. As opposed to "2+2 is somewhere between 1 and 7, depending on the direction your inner chi flows today"...

A rather good fit for a technical forum, don't you think?

Yeah, exactly... "2+2 equals 4" kind of person.

No not at all. It strikes me as you are a 2+2 = 4.00000000000000001 kind of person.

A rather good fit for a technical forum, don't you think?

No. :) Your main mission seems to be to confuse beginners. I understand that AVR Freaks might be a better place for you.

Montmorency:
Whatever the compiler decides to do if it detects the violation. It is hard to predict how the gears in the optimizing compiler’s brain will mesh for code with undefined behavior.

This is my quick and dirty test program and the output is ALWAYS exactly what I predicted in Reply #4

// python-build-start
// action, upload
// board, arduino:avr:uno
// port, /dev/ttyACM0
// ide, 1.8.6
// python-build-end

int testNum = 0;
bool printVals =  false;

void setup() {
    Serial.begin(500000);
    Serial.println("<Starting  >");

    Serial.print("<Compiled ");
    Serial.print(__DATE__);
    Serial.print(' ');
    Serial.print(__TIME__);
    Serial.println('>');

}

void loop() {
    testNum++ ;
    if (testNum > 32764) {
        printVals = true;
    }
    else if (testNum > -32764) {
        printVals = false;
    }
    if (printVals == true) {
        Serial.println(testNum);
    }
}

Sample of output

<Starting  >
<Compiled Apr 16 2019 08:36:53>
32765
32766
32767
-32768
-32767
-32766
-32765
-32764
32765
32766
32767
-32768
-32767                                                                          
-32766                                                                          
-32765                                                                          
-32764

IMHO the behaviour here is a function of the design of the Atmega 328 chip and little or nothing to do with the compiler.

…R