4

I was just curious about how the AVR architecture manages errors that would cause a regular desktop program to crash. I'm talking about logical errors for example math problems that are undefined. Such as division by 0 and getting a square root of a negative number. At first I was expecting that by giving an error to the AVR chip it will just return 0. But I ran this program:

void setup() {
Serial.begin(9600);
}
void loop(){
int x = 0;
int p = 50;
int result;
result = p/x;
Serial.println(result);
result = sqrt(-10);
delay(1000);
Serial.println(result);
delay(10000);
}

and got this output:

4294967295
0

After this I got really confused. Why would the result variable take the maximum value of an unsigned long? Since I declared an int the micro controller must have dedicated 2 bytes of dynamic memory to this variable but apparently it somehow managed to get additional 2 bytes to store the data. Could this mean that the AVR chip can corrupt data in other memory locations while allocating new data for the variable that just was fed a undefined in to it? Okay maybe the AVR chip just sets any mathematical nonsense to 4294967295. But no in the example of getting the square root of -10 we see that the value became 0. Although this is probably a function processed by some library so maybe there is some kind of protection against these errors. Also, I tried to run the program above but with a byte variable for the result instead of int.

void setup() {
Serial.begin(9600);
}
void loop(){
int x = 0;
int p = 50;
int result;
result = p/x;
Serial.println(result);
result = sqrt(-10);
delay(1000);
Serial.println(result);
delay(10000);
}

And the result was the same. So, is there some kind of documentation about error management in AVR since this is important to know while development.

P.S. Everything was run on a real Atmega 328p chip on an arduino nano.

Coder_fox
  • 686
  • 7
  • 14

2 Answers2

8

The simple answer is: they are not handled at all.

According to the C and C++ standards, what you are invoking is called undefined behavior, meaning anything can happen. In practical terms, it means that the compiler can do its optimizations assuming you will never invoke undefined behavior, and completely disregard what could happen if the assumption does not hold. The generated code could be completely broken, it won't be the compiler's fault.

The AVR architecture cannot do divisions, nor floating point math. Thus all the errors you have here happen at the software level. Note that sqrt(-10) is not an error (it's NaN, which is correctly handled by the avr-libc), but converting that to an int (which you do when you assign it to result) is an error.

If you really want to know the details of what happens with each of these errors, there is only one option: you have to disassemble the executable program and read the assembly listing.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
1

About the number 4294967295

To my surprise, the sketch produces indeed the number 4294967295. That is not okay.
It turns out that the compiler knows the numbers and tries to do the math itself (instead of runtime).

At runtime, the 50 / 0 results into a signed integer of -1.
When a byte is used, the 50 / 0 results into 255.
The result of that calculation is a variable with only 1's. That is 0xFFFF for a 16-bits variable. That results into 65535 for a unsigned integer and -1 for a signed integer and 255 for a unsigned byte.

So far, that is okay. It makes sense.

However, when the compiler tries to do the math itself with 50 / 0, it turns the result into a 32-bit variable of 0xFFFFFFFF. The compiler does not put the math into the binary result, but calls the Serial.println() directly with a 32-bit unsigned long of 0xFFFFFFFF, which is 4294967295.

The reason is not the variable type of the variable 'result', but the division with 'p/x'. The compiler thinks it is better to make a 32-bit integer when the math is done with two 16-bit integers.

I think that when the compiler optimizes, the result should be same as a runtime calculation.
There is an interesting discussion below this answer. @EdgarBonet calls the compiler behaviour weird but not a bug.

Below is a test sketch that produces 4294967295 with Arduino 1.8.7 for an Arduino Uno:

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

void loop(){
  int x = 0;
  int p = 50;
  int result;
  result = p/x;
  Serial.println(result);
  delay(5000);
}

The code outputs normal results when 'x' or 'p' is calculated, for example the result from a function, or when one of the three variables is made volatile.

Jot
  • 3,276
  • 1
  • 14
  • 21