Overload of the counter-variable in a for-loop

Hello all,

From the Arduino-Reference I have the following information about integers:

"On the Arduino Uno (and other ATmega based boards) an int stores a 16-bit (2-byte) value. This yields a range of -32,768 to 32,767 (minimum value of -2^15 and a maximum value of (2^15) - 1)."
see int - Arduino Reference

I work with an Arduino Uno (ATMEGA328P) and an Arduino Mega (ATMEGA 2560) and tried to demonstrate that by a simple code:

void setup()
{
Serial.begin(9600);
}

void loop()
{
for (int i = 32760; i<=40000; i++)
Serial.println(i);
}

Running the code my Serial Monitor prints the following values:

32760
32761
32762
32763
32764
32765
32766
32767
32768
32769
32770
32771
32772
32773
32774
...

I expected that i (which is an integer!) will start from -32768 once it becomes greater than 32767 but as you can see it doesn't. Has anyone an idea why there is no overload?

Thank you so much for any help!

Please use code tags. Thank you

I don't have an Arduino available at the moment, but can see that this example

#include <stdio.h>
#include <stdint.h>


int main()
{
	for (int16_t i = 32760; ; i++) {
		printf("%d\n", i);
		if (i < 0) 
			return -1; // stops here
		if (i > 32777) return i;
	}
	return 0;
}

behaves as expected, and produces this output:

32760
32761
32762
32763
32764
32765
32766
32767
-32768

BTW: We're talking about overflow, not overload

The problem has to do with optimization.

When I make the i volatile the variable i wraps around as expected and the endcondition is never reached.
Note it never prints "exit loop"

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  for (volatile int i = 32760; i<=33000; i++)  Serial.println(i);

  Serial.println("exit loop");     
}

further analysis is needed ...

Hello robtillaart,

thank you for your message and sorry I didn't use code tags - this is my very first post here...

I get the same results using your code with volatile. The effect of volatile makes me very wonder and I have no explanation why the results change. However I will go on and try to find out why this happens.

Mike777

making a variable volatile means that the compiler generates code that writes (& reads) the variable back from its registers to RAM after every change. IT does not optimize the performance by keeping an often used variable in its registers.

So it looks like the int i code in the for loop is optimized and stored in a 32 bit register which can hold values > 32767 and never written back. So it never sees it does not fit into a 16 bit var.

Still it is very strange and should imho not happen.

The compiler sees the 40000 value as not being able to fit in an int, so it uses 32 bit registers for the loop index, even though the initial value is typed as an int. Nothing strange about the results that either of you are seeing.

PaulS:
The compiler sees the 40000 value as not being able to fit in an int, so it uses 32 bit registers for the loop index, even though the initial value is typed as an int. Nothing strange about the results that either of you are seeing.

But why does the compiler not use 32 bit registers if I change int in uint8_t ? (see code below)

Or uint16_t? ( and use 65530 an 65540)

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  for (uint8_t i = 250; i<=260; i++)  
  {
    Serial.println(i);
  }

  Serial.println("exit loop");     
}

and why does the compiler not use 32bit registers here in the same way?

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  for (int i = 32760; i<=32770; i++)  
  {
    Serial.println(i*2);
     Serial.println(i);
  }

  Serial.println("exit loop");     
}

I still think it is an optimizing bug.

Can you point me to a (c++ spec) document that describes this behavior as correct?

Note the compiler gives a warning

C:\Users\Rob\Desktop\WORK\Arduino\sketch_jan23c\sketch_jan23c.ino: In function 'void loop()':

C:\Users\Rob\Desktop\WORK\Arduino\sketch_jan23c\sketch_jan23c.ino:8:24: warning: comparison is always true due to limited range of data type [-Wtype-limits]

[color=red]   for (int i = 32760; i<=32770; i++)
                        ^ [/color]

So the compiler sees the limited range of the datatype... ? but decides to overrule?

Furthermore the compiler does the while correct - equivalent to for loop except for the scope of i

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int i = 32760;
  while(i <= 32770)  
  {
    Serial.println(i);
    i++;
  }

  Serial.println("exit loop");     
}

http://en.cppreference.com/w/cpp/language/ub

"undefined behavior - there are no restrictions on the behavior of the program. Examples of undefined behavior are memory accesses outside of array bounds, signed integer overflow, null pointer dereference, modification of the same scalar more than once in an expression without sequence points, access to an object through a pointer of a different type, etc. Compilers are not required to diagnose undefined behavior (although many simple situations are diagnosed), and the compiled program is not required to do anything meaningful."

Thanks John,
it will be an interesting read... Still strange it only happens when i is an int and not for other datatypes.

Try this in 1.8.5, then make "i" unsigned:

void setup()
{
  Serial.begin(9600);
  for (int i = 32760; i < 32800; i++) // uint16_t
  {
    Serial.println(i);
    delay(100); 
  }
}

void loop()
{
        
}

Why I don't like to upgrade, you think everything is OK, then one day, GOTCHA!

outsider:
Why I don't like to upgrade, you think everything is OK, then one day, GOTCHA!

Only a problem if you depend on Undefined, Unspecified, or Implementation-defined behavior. :slight_smile:
The folks who were doing "i = i++;" found that out the hard way in the switch from 1.0 to 1.6. In 1.0 it works like "i++;" but in 1.6 it works like "i = i;"

It's been forever since I've so much as looked at a c/c++ spec, but I'm fairly certain it doesn't say ANYTHING about the internal representation of numbers. It almost certainly does not specify WHAT any number does when you perform an operation that results in exceeding its maximum value. So, what is shown here is almost certainly perfectly legal.

Counting on a specific behavior when you do something that doesnt make sense is a little silly, and will, at a minimum, be compiler and platform dependent.

Regards,
Ray L.

Adding an extra read reference to i gives expected behavior

void setup()
{
  Serial.begin(9600);
  for (int i = 32760; i < 32800; i++) // uint16_t
  {
    Serial.println(i);
    Serial.println(2*i);     // enable/disable this line to see different loop behavior.
    delay(100); 
  }
}

void loop(){}

Note this extra line does not assign any value to i so the compiler could just keep i optimized in a register.

found this read - https://www.cs.utah.edu/~regehr/papers/overflow12.pdf
it states exactly that INT_MAX + 1 is undefined and that is what seems to happen here.

But why does the compiler not use 32 bit registers if I change int in uint8_t ? (see code below)

Why should it? The initial value and termination value are 16 bit values. The compiler uses the smallest register type possible that will handle all the values.

PaulS:
Why should it? The initial value and termination value are 16 bit values. The compiler uses the smallest register type possible that will handle all the values.

OK, rephrase, why does it now uses a uint16_t type register for i while it otherwise uses an int16_t type register.

OK, rephrase, why does it now uses a uint16_t type register for i while it otherwise uses an int16_t type register.

The compiler saw that you wanted to use unsigned registers for the loop index, because your type was uint8_t. It also saw that 260 was a not a byte sized value, so it knew that the register size needed to be larger than 8 bits.

So, why wouldn't the compiler take note of both facts, and use unsigned 16 bit registers and generate appropriate instructions to use unsigned 16 bit processing?

PaulS:
The compiler saw that you wanted to use unsigned registers for the loop index, because your type was uint8_t. It also saw that 260 was a not a byte sized value, so it knew that the register size needed to be larger than 8 bits.

So, why wouldn't the compiler take note of both facts, and use unsigned 16 bit registers and generate appropriate instructions to use unsigned 16 bit processing?

For me that is simple, the compiler should not assume what I meant and autocorrect. It should beat me up if I provide stupid code, treat warnings as errors (as the warning of today is the error of tomorrow)

Otherwise it may add al those ; I forget after an evening of python or remove the ; when it should not be there e.g. after a for (...); break; forgotten in switch statements. etc.