Strange unsigned problem

Hi! I’m working on a routine for reading data from an eeprom byte per byte.
I’m using the following code in an include file:

  template <typename T>
  uint8_t read(const uint16_t& address, T& value) const {
    uint8_t i{0};
    uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
    for (i = 0; i < sizeof(T); i++) {                              // Loop for each byte to be read
      *bytePtr++ = ePage[address>>8]->readByte((address + i) & 0xFF);// Read a byte
    }
    return i;
  }

ePage is an array of I2C_eeprom.

The function works properly with unsigned numbers. But if i try to read a signed number (an int8_t) the function return in the value field a 4 byte result.
I’ve tryied using an union instead of the pointers, and do the same thing.
The issue is reproducible here:
https://rextester.com/l/c_online_compiler_gcc
inserting the following code:

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

int main(void) {
  int8_t value = 1;

  uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
  printf("Value = %X\r\n",value);
  *bytePtr = 0xFA; // Read a byte
  printf("Value = %X\r\n",value);
    
  return 0;
}

How is possible that value (that is an 8bit variable) return a 4 byte result? 4bytes maybe are the byte of the pointer (or maybe the 32bit as the mcu have a 32bit architecture), but… why?
And here, the same with unions:

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

int main(void) {
  int8_t value = 1;
  union {
    int8_t v;
    char o[sizeof(int8_t)];
  } x;
      
  //uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
  x.v = value;
  printf("Value = %X\r\n",x.v);
  x.o[0] = 0xFA; // Read a byte
  printf("Value = %X\r\n",x.v);
    
  return 0;
}

The problem is present only with int8_t datatype. If i use int16_t or int32_t all works as it should.

What’s going on?
Thanks!

I’m not sure what is going on here but ...I didn’t think you could have a signed byte value only int_16 and above could be signed ??

Any reason for not using the EEPROM Library ?

You need to post all of your code !

I've not posted all the code as it's reproducible only by that part of the code. It's sufficient to past the code in the online gcc and watch the result.
I not use the eeprom library because my hardware have four 255 byte eeprom addresses, and i've created a function to see them as a unique 4k eeprom. So i read byte per byte, and if the variable is half in one eeprom bank, and the other half in another, the function take care of it.
In any case, seem a bug related only to the int8_t datatype. For now i avoid it in eeprom variables (at the espense of wasting one byte).

"four 255 byte eeprom addresses, and i've created a function to see them as a unique 4k eeprom"

Is that a typo? 4 x 255 = 1k. (well, 4 x 256 actually, for 1024 addresses, = 1k).

Spark fun have a library for external I2C eeprom

CrossRoads:
"four 255 byte eeprom addresses, and i've created a function to see them as a unique 4k eeprom"

Is that a typo? 4 x 255 = 1k. (well, 4 x 256 actually, for 1024 addresses, = 1k).

Yes, typo sorry!

In any case also int16_t have the same problem. When i write the first byte all ok, when i write the second one, all goes wrong

Always using this onine gcc compiler:
https://rextester.com/l/c_online_compiler_gcc

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

uint8_t test(uint16_t address, int16_t value);

int main(void)
{
   int16_t val = 1;
    test(101,val);
    return 0;
}

  uint8_t test(uint16_t address, int16_t value)  {
    uint8_t i;
    uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
    for (i = 0; i < sizeof(value); i++) {                              // Loop for each byte to be read
      printf("Value = %X\r\n",value);
      *bytePtr++ = 0xFA; // Read a byte
      printf("Value = %X\r\n",value);
    }                                                              // of for-next each byte
    return i;
  }

returns

Value = 1
Value = FA
Value = FA
Value = FFFFFAFA

It must return a 2 byte value…

You're printing the signed number as an unsigned value. That confuses printf(). It is promoting it to a longer data type before printing.

I think the problem might be in the formatting for print. If you use Serial.print() on a signed byte containing 0xFF (-1) it gets sign-extended to 0xFFFF in the promotion to ‘int’ and Serial.print(int x) then passes the value to Serial.print(long x) so it gets sign-extended again to 0xFFFFFFFF. If you are displaying in DEC you see “-1” but if you use “HEX” you see “FFFFFFFF” and if you use “BIN” you see “1111111111111111111111111111111”. All have “the same value” as your signed byte of 0xFF (-1).

Beware of sign extension.

aarg:
You're printing the signed number as an unsigned value. That confuses printf(). It is promoting it to a longer data type before printing.

Uhm, i'm printing the hex value (%X), the hex rappresentation has no sign. In case of signed 8 bit 0x01 is 1 and 0xff is -1.
And in any case it's not only a formatting error, as i've spotted it because the read value hasn't the correct data on the formulas that use it. In case of int16_t i have to read 0x00 0x01 bytes (decimal 1), instead i get -1, the same as the printf (that return always 4 bytes).

look at this one:

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

uint8_t test(uint16_t address, int16_t value);

int main(void)
{
   int16_t val = 1;
    test(101,val);
    return 0;
}

  uint8_t test(uint16_t address, int16_t value)  {
    uint8_t i;
    uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
    for (i = 0; i < sizeof(value); i++) {                              // Loop for each byte to be read
      printf("Value = %X\r\n",value);
      *bytePtr++ = 0xFA; // Read a byte
      printf("Value = %X\r\n",value);
    }                                                              // of for-next each byte
    if (value == 0xFAFA) value = 0x1111;
    printf("Value = %X\r\n",value);
    return i;
  }

return

Value = 1
Value = FA
Value = FA
Value = FFFFFAFA
Value = FFFFFAFA

but if i change

if (value == 0xFAFA) value = 0x1111;

with

if (value == 0xFFFFFAFA) value = 0x1111;

it return

Value = 1
Value = FA
Value = FA
Value = FFFFFAFA
Value = 1111

So for sure now the previously defined int16_t has “become” a 32bit variable… But if i do a sizeof(value) before and after the byte placement, i get always 2 as result.

masterx81:
So for sure now the previously defined int16_t has "become" a 32bit variable... But if i do a sizeof(value) before and after the byte placement, i get always 2 as result.

When you compare a signed 16-bit value with a 32-bit value it gets sign-extended first. Your 8-bit variable containing -6 (0xFA), when used in an expression, gets promoted to an 'int' containing -6 (0xFFFA). And when compared to a 'long' gets promoted to a 'long' containing -6 (0xFFFFFFFA).
If you are interested in bit patterns or unsigned numbers, use unsigned variables.

johnwasser:
When you compare a signed 16-bit value with a 32-bit value it gets sign-extended first. Your 8-bit variable containing -6 (0xFA), when used in an expression, gets promoted to an 'int' containing -6 (0xFFFA). And when compared to a 'long' gets promoted to a 'long' containing -6 (0xFFFFFFFA).
If you are interested in bit patterns or unsigned numbers, use unsigned variables.

Whoa, i not develop in c, but i was unaware of the promote to bigger size thing. So also if i declare a variable 1 byte long, every time that i compare it (only compare, or also other math operators?) with bigger variable, the code increase it's size? How i can avoid this? Putting (for example) an "(int8_t)" before the bigger variable?

Can you think of a good reason to compare a single byte variable with a two or four byte variable?

I can't.

masterx81:
Whoa, i not develop in c, but i was unaware of the promote to bigger size thing.

When you are programming in C or C++ (like with Ardiuino) you will have to become aware.
If you don't want your values to be promoted, don't use any data type smaller or larger than 'int'.

johnwasser:
When you are programming in C or C++ (like with Ardiuino) you will have to become aware.
If you don’t want your values to be promoted, don’t use any data type smaller or larger than ‘int’.

Lesson learnt, thanks! I will continue to use the smaller variables as possible for optimize the code…

In any case, i’ve put the whole code here: GitHub - masterx1981/EEPROM_Ind: EEPROM Routines for Industruino
But it’s almost the same as the one in post #1

And… The problem persists. Also without doing an operation that compare the content or that can promote the variable to a bigger size (or maybe there is one that i not notice?) it return a 32 bit variable

the output of this code:

    //test bug unsigned int
    uint8_t cnt;
    int8_t test = 1;
    SerialUSB.println("Before read");
    cnt = eeprom.read(0, test);
    if (test == 0xFF)  SerialUSB.println("Is 0xFF!!");
    SerialUSB.print("After read = ");
    SerialUSB.println(test,HEX);
    SerialUSB.println(test);

return

Before read
After read = FFFFFFFF
-1

as you see it miss the " if (test == 0xFF) ", and i’m totally sure that at eeprom location 0 there is an 0xFF (confirmed by the -1 printed for last).
And, as told, i’ve also tried using union with same result…
If i write a signed number like 0x01, all works perfectly.

------EDIT------

Some more tests:

    int8_t test = 1;
    SerialUSB.print("Before read HEX = ");
    SerialUSB.println(test,HEX);
    SerialUSB.print("Before read DEC = ");
    SerialUSB.println(test);
    SerialUSB.print("Sizeof = ");
    SerialUSB.println (sizeof(test));
    cnt = eeprom.read(0, test);
    if (test == 0xFF)  SerialUSB.print("Is 0xFF!!");
    SerialUSB.print("After read HEX = ");
    SerialUSB.println(test,HEX);
    SerialUSB.print("After read DEC = ");
    SerialUSB.println(test);
    SerialUSB.print("Sizeof = ");
    SerialUSB.println (sizeof(test));
    SerialUSB.println("");
    SerialUSB.print("Before read HEX = ");
    SerialUSB.println(test,HEX);
    SerialUSB.print("Before read DEC = ");
    SerialUSB.println(test);
    SerialUSB.print("Sizeof = ");
    SerialUSB.println (sizeof(test));
    cnt = eeprom.read(101, test);
    if (test == 0xFF)  SerialUSB.print("Is 0xFF!!");
    SerialUSB.print("After read HEX = ");
    SerialUSB.println(test,HEX);
    SerialUSB.print("After read DEC = ");
    SerialUSB.println(test);
    SerialUSB.print("Sizeof = ");
    SerialUSB.println (sizeof(test));

and the result

Before read HEX = 1
Before read DEC = 1
Sizeof = 1
After read HEX = FFFFFFFF
After read DEC = -1
Sizeof = 1

Before read HEX = FFFFFFFF
Before read DEC = -1
Sizeof = 1
After read HEX = 1
After read DEC = 1
Sizeof = 1

Location 0 have 0xFF, location 101 have 0x01.

and the same on the online GCC compiler.

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

int main(void) {
  int8_t value = 1;

  uint8_t* bytePtr = (uint8_t*)&value;                           // Declare pointer to start of structure
  printf("Value HEX = %X\r\n",value);
  printf("Value DEC = %d\r\n",value);
  printf("Size = %d\r\n",sizeof(value));
  *bytePtr = 0xFA; // Read a byte
  if (value == 0xFA) printf("OxFA!!\r\n",value); 
  printf("Value HEX = %X\r\n",value);
  printf("Value DEC = %d\r\n",value);
  printf("Size = %d\r\n",sizeof(value));
   
  return 0;
}
Value HEX = 1
Value DEC = 1
Size = 1
Value HEX = FFFFFFFA
Value DEC = -6
Size = 1

Every time that i touch byte per byte a nevagive variable, I can’t test the returned value using it’s original size, or if i test it, i need to test it as 32bit.

In any case, happy new year!!

masterx81:
Every time that i touch byte per byte a nevagive variable, I can't test the returned value using it's original size, or if i test it, i need to test it as 32bit.

Part of your problem is that '0xFF' is NOT an 'int8_t' or 'uint8_t' value. There are NO 8-bit integer literals:
https://en.cppreference.com/w/cpp/language/integer_literal

Since 0xFF (255) fits in an 'int' it is an integer literal containing '0x00FF'. To use it as an 'int8_t' you should cast it:

if (test == (int8_t)0xFF)  SerialUSB.print("Is 0xFF!!");

The 'test' value, which IS an 'int8_t' gets sign-extended to an 'int' of 0xFFFF. That is clearly not equal to the integer literal containing 0x00FF.

If you don't want your 'test' value to be sign-extended, make it unsigned.

Perfect, this time i've understood it, all makes sense!
Really thanks!

I've never worked a lot with C, and now i've some problems :disappointed_relieved: :disappointed_relieved:

masterx81:
Perfect, this time i've understood it, all makes sense!
Really thanks!

I've never worked a lot with C, and now i've some problems :disappointed_relieved: :disappointed_relieved:

It's not so bad, if you mostly follow the conventions. If you use data types that are appropriate for the data you expect, and don't try to use data conversions as a "manipulation tool", you won't have very many problems. C lets you get away with things that are forbidden in other languages. So it kind of dumps more responsibility in your lap.