Data type int16_t/int becomes 32-bit int if optimizer uses register?

Hi,

I wanted to show my students the value range of a 16-bit int.
IMHO the following code doesn't behave correctly:

int main()
{
  init();
  Serial.begin(9600);
  int16_t a=32760;
  // force var a to be from memory class "auto"
  //   if you want the address of a variable, the gcc optimizer
  //   cannot use a register:
  // Serial.print("Adress of a: "); Serial.println((unsigned int) &a);  
  Serial.print("Sizeof a: "); Serial.println(sizeof(a));
  while (a<=-32760 || a>=32760)
  {
    Serial.println(a);
    a++;
  }
  while (1) { }
  return 0;
}

The first output shows that the sizeof(a) is 2 (16-bit). The value range is of a 32-bit int.

As far as I can see, this is a problem of the avr-ggc optimizer.
If storage class register is use then int/int16_t gets the value range of a 32-bit value. If storage class auto (variable on stack) is use the programm behaves IMHO correctly.
To show this, I added the statement

Serial.println((unsigned int) &a);  

so the optimizer cannot use the storage class register. Then the code behaves correctly.

The data types short int or unsigned int/uint16_t don't have this "flaw".

The installed version is:
arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7/bin/avr-g++

The storage class shouldn't change the value range of a data type. Or do I miss sth.?

1 Like

Have you checked compiler warnings about type extension or the like? Computations can be done with increased precision until the result is stored in a variable.

How is uint16_t defined on your machine?

Which optimization level did you specify?

It's not clear what you're expecting or getting.
Serial.print() for int has a somewhat sketchy definition:

size_t Print::print(int n, int base)
{
  return print((long) n, base);
}

I believe that the behavior of C in the presence of signed integer overflow is "undefined." So anything is "legal"... ( Integer Overflow Basics - Autoconf )

1 Like

I'm expecting the following:

Address of a: 2298
Sizeof a: 2
32760
32761
32762
32763
32764
32765
32766
32767
-32768
-32767
-32766
-32765
-32764
-32763
-32762
-32761
-32760

This can be seen if the address of the variable "a" will be outputted.

If I don't ask for the address of variable "a" I will get the following:

Sizeof a: 2
32760
32761
32762
32763
32764
32765
32766
32767
32768
32769
32770
...

(and so on...)

Outputting INT16_MAX and UINT16_MAX gives me:

INT16_MAX: 32767
UINT16_MAX: 65535

But thanks for the link you sent me.

This makes it clear:

...
In contrast, the C standard says that signed integer overflow leads to undefined behavior where a program can do anything, ...
In practice all known implementations support silent wraparound in this case, so you need not worry about other possibilities.

So with Arduino Uno it differs and "you have to worry about it", because it depends on the storage class.
auto, static, extern support silent wraparound on int, but if the optimizer uses register then the int16_t doesn't wraparound on 32767+1, but extends the value range of a regular int, using 4 Atmel register instead of 2.

ANSI-C guarantees the wraparound only for unsigned integer types.

So thanks again for the link.

Couldn't it be that only a signed int is converted into an unsigned int, of still 2 bytes? Can you check that in your code?

BTW what if you declare a volatile?

and there is also a compiler switch to force the wraparound on signed integers:
-fwrapv

which gives me the result I expected.

No, because if I change the line

a++;

to

a+=10000;

I get the following:

Sizeof a: 2
32760
42760
52760
62760
72760
82760
92760
102760
112760
122760
132760
142760
152760
162760
172760
...

but the compiler flag "-fwrapv" solves this issue.

Of course declaring the variable volatile or static will also work in this case, because it will use a memory storage.

(see ISO/IEC 9899:1999 (E) Footnote 114:
A volatile declaration may be used to describe an object corresponding to a memory-mapped
input/output port or an object accessed by an asynchronously interrupting function. Actions on
objects so declared shall not be ‘‘optimized out’’ by an implementation or reordered except as
permitted by the rules for evaluating expressions.)

But the problem with undefined behaviour on signed integer overflow isn't addressed with this, because as far as I understand the Integer Overflow Basics it could also happen with a variable in the RAM (even if it will be handled correctly with the actual compiler).

Thanks for your suggestions.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.