Logical not behaving strangely with bool variable values containing other than 0 or 1

If I use the logical not operator (!) on a bool variable then should true turn into false and vice versa?

Seems reasonable.

But there are ways that a bool can contain a bit pattern other than 0 or 1. In my case I am using EEPROM.get to read from eeprom into a bool and it can happen that the eeprom contents are not 0 or 1 (ie 2 to 255 inclusive) meaning the value is true. If I then try to change the bool value using ! the value stays true. It looks like the ! operator only flips the state of the least significant bit. So if any of the other bits are initially set they remain unchanged and the value remains true.

#include <EEPROM.h>

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  bool freeMode;

  EEPROM.update(0, 0);

  EEPROM.get(0, freeMode);

  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }

  freeMode = !freeMode;
  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }

  freeMode = !freeMode;
  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }


  EEPROM.update(0, 255);

  EEPROM.get(0, freeMode);

  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }

  freeMode = !freeMode;
  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }

  freeMode = !freeMode;
  if (freeMode) {
    Serial.println("True");
  } else {
    Serial.println("False");
  }
}

void loop() {
  // put your main code here, to run repeatedly:
}

I appreciate that the logical not operator can be used on integer variables in general and in these cases it's perhaps debatable whether the ! operator should be changing them into 0 or 1 but for a bool variable should this not be the case?

I'd appreciate it if anyone else could confirm they observe this behaviour.

I'd like to say that I'm not interested in workarounds. I've already done that by checking the value of the bool and setting it explicitly to true or false as appropriate. I simply think that applying the logical not operator to a bool variable should change it to the correct state.

Dump the value of freemode instead of just testing, to see what you're actually getting.

I can not reproduce this. [edit] I think I see what you mean. The following snippet,

uint8_t c = 10;
bool& b = reinterpret_cast<bool&>(c);

Serial.print(c, 2);
Serial.print(' ');
Serial.println(b);

b = !b;

Serial.print(c, 2);
Serial.print(' ');
Serial.println(b);

b = !b;

Serial.print(c, 2);
Serial.print(' ');
Serial.println(b);

gives:

1010 0
1 1
0 0

When c is initialised to 11 though, we see the following:

1011 1
0 0
1 1

So apparently, only the least significant bit is used to evaluate the value of a boolean. The not operator however does not seem to flip only this bit.

[edit2]

The following is even stranger (and seems to have the behaviour described in post #1).

uint8_t c = 10;
bool& b = reinterpret_cast<bool&>(c);
bool b_ = b;

for (size_t i = 0; i < 3; i++) {
  Serial.print(c, 2);
  Serial.print(' ');
  Serial.print(b);
  Serial.print(' ');
  Serial.println(b_);

  b = !b;
  b_ = b;
}

output:

1011 11 1
1010 10 10
1011 11 11

Freemode is of type bool, so whatever you try to push in will be casted to bool (0 or 1).
All integers will evaluate to 1 (true), except 0. Zero will evaluate to 0 (False).
If you want to flip all bits you can use ~.
01010101 will then turn into 10101010.
I guess it can also take an int.

I’m sorry, what your sketch is supposed to show? First time you set eeprom to 0 second to 255 but all your further variable manipulation are not with eeprom memory

i get following

 10 0

isn't that what you expect?

#include <stdio.h>

int
main ()
{
    int  c = 10;
    printf (" %d %d\n", c, !c);
}

You cannot do this, it's an invalid cast, and the example you posted invokes undefined behavior, so the results don't mean anything.

The original code in the OP also invokes undefined behavior: the type bool only has two values: true and false. You are not allowed to overwrite a bool variable with a different value representation than the ones for true and false.

Note the difference between the conversion from integers to bool and the value representation of bool: on the one hand, the language defines conversions from integers to bool, where the integer value of zero is mapped to false, and all other values are mapped to true.
On the other hand, the type bool has the same value representation as some integer type (usually unsigned char), but the only two allowed values are true and false (usually with a value representation of 1 and 0 respectively, but this is not guaranteed).
If you assign an integer to a bool, you get the conversion, so starting with a value other than 1 or 0 is fine.
If you copy the value representation of an integer to a bool, then the result is undefined if the value representation does not represent either true or false. The latter situation can happen if you're using memcpy on bools, or reading them from EEPROM like you're doing here.

You should create a variable of type uint8_t, read that from EEPROM (because you know that each possible value representation you may read from EEPROM will represent a valid value for the type uint8_t), and then convert it to bool.

uint8_t freeModeInt;
EEPROM.get(0, freeModeInt); // overwrites the value representation of freeModeInt
bool freeMode = freeModeInt; // conversion from int to bool
1 Like

I am aware of this. This example is only to see what happens under the hood and to confirm the observations in post #1.

Can you show your printout?
The behaviour demonstrated by @gcjr is what I would expect...
So how is that different?

Edit: not needed anymore as issue is solved...

It doesn't show you what happens under the hood, it's just Undefined Behavior, the compiler can generate anything, so it doesn't even tell you what the hardware is doing in this case.

I have learned something today!
Thanx!

It was remiss of me not to include the contents of a run of the code. I've changed it to dump the actual value of the bool at key stages and only included the case where I think the compiler behaves curiously.

#include <EEPROM.h>
#include <Dump.h>

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  bool freeMode;

  EEPROM.update(0, 255);

  EEPROM.get(0, freeMode);

  Serial.print("Contents of bool loaded with 255");
  dumpRam(Serial, &freeMode, 1);

  freeMode = !freeMode;
  Serial.print("Contents of bool after logical not");
  dumpRam(Serial, &freeMode, 1);


  freeMode = !freeMode;
  Serial.print("Contents of bool after logical not");
  dumpRam(Serial, &freeMode, 1);
}

void loop() {
  // put your main code here, to run repeatedly:
}

This gives:

12:29:04.164 -> Contents of bool loaded with 2550x08FB: . FF
12:29:04.196 -> Contents of bool after logical not0x08FB: . FE
12:29:04.291 -> Contents of bool after logical not0x08FB: . FF

So the logical not only flips the least significant bit and the bool stays true.

At the very least I think this behaviour should be documented. I'd prefer it if the compiler changed all the bits when it did a logical not on a bool.

Which is what is should do because only the least significant bit changes between a true or false.

OK I am sure the standards C committee will change things just to please your sensibilities.
See about the current C standard here
C17 standard

Do you have compiler warnings on?
The (mem)copy of the byte into a bool might give a warning on undefined behaviour...

There is no compiler warning.

I'm not very familiar with the macro environment here but I guess this should be updated to prevent a bool being used with EEPROM.get()

    //Functionality to 'get' and 'put' objects to and from EEPROM.
    template< typename T > T &get( int idx, T &t ){
        #if defined(__has_include) && __has_include(<type_traits>)
        static_assert(std::is_trivially_copyable<T>::value,"You can not use this type with EEPROM.get" ); // the code below only makes sense if you can "memcpy" T
        #endif
        EEPtr e = idx;
        uint8_t *ptr = (uint8_t*) &t;
        for( int count = sizeof(T) ; count ; --count, ++e )  *ptr++ = *e;
        return t;
    }

from: EEPROM/EEPROM.h at master · PaulStoffregen/EEPROM · GitHub

Arduino code is compiled using a C++ compiler. Here's the latest C++ standard:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4868.pdf

But indeed, there's no way the committee will change the behavior here: you absolutely don't want to perform an int-to-bool conversion on each access of a boolean variable. For performance, it makes perfect sense for the compiler to assume that each boolean variable contains a valid value (i.e. true or false) and not some arbitrary integer.

The problem is that memcpy'ing a bool is not wrong per se, it depends on the value you're trying to copy.

I agree.

https://www.arduino.cc/reference/en/language/variables/constants/constants/

true is often said to be defined as 1, which is correct, but true has a wider definition. Any integer which is non-zero is true, in a Boolean sense. So -1, 2 and -200 are all defined as true, too, in a Boolean sense.

So changing a bit other than the LSB may (depending on the other bits) change its logical value for the Arduino IDE.

I didn't think the Arduino IDE made any claims to be compliant to this C (or a C+) standard. That's fine by me as long as it documents what it does do.

This quote talks about the “boolean” truthiness of integers, not about the value representation of the datatype bool.

// This is fine:
bool b = 123; // b == true
// This is not:
uint8_t i = 123;
bool b;
memcpy(&b, &i, 1); // undefined behavior

The Arduino IDE simply adds #include <Arduino.h> at the top of your sketch, possibly adds some function prototypes, and then passes it on to GCC's C++ compiler. The code you write in the Arduino IDE is C++ code.

In my real code however the bool is part of a Struct containing a whole variety of data types and I EEPROM.get the whole struct.

So disallowing EEPROM.get to read bools would not prevent this behaviour.

Yes, it looks like you are right. It is certainly an interesting problem. It shows up especially with the EEPROM because, by default or factory condition, all bits are set to '1' so any attempt to read a struct, with miscellaneous data types, which has not been initialised, will go wrong for bool (as you have shown) or c strings where there will be no \0 terminator and maybe others.

Incidentally, once a bool gets rubbish in it, that is a value of neither 0 nor 1, it is not simple to clean it up in place:

  bool freeMode;

  EEPROM.update(0, 255);
  EEPROM.get(0, freeMode);

  freeMode = EEPROM.get(0, freeMode); // doesn't work
  freeMode = freeMode ;  // doesn't work
  freeMode = !! freeMode ; // doesn't work
  freeMode = ! freeMode ; freeMode = ! freeMode ;  // doesn't work
  freeMode = freeMode > 0 ;  // doesn't work