Bitwise NOT Operator ~ (Binary Ones Complement) on an "if" statement

Go To Last Post
27 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Toolchain: (AVR/GCC - 3.4.1067) Native of AtmelStudio 6.2

 

I have a very, very, basic question regarding the bitwise not operator (~) on if statements, I hope someone can give me a hand:

 

I noticed I was making a mistake on a code with a logic similar to the following:

    unsigned char _a = 0x00;
    unsigned char _b = 0xFF;

    unsigned char a = _a;
    unsigned char b = _b;		

    if (a != ~b)
    {
        printf("test!/n"); // "test" is printed but it should not
    }

I changed the variable type to "signed char" and the problem went away, so I guess it makes sence: how to get a binary ones compliment of an unsigned variable...

 

My question is why the compiler allows me to do the following then:

    unsigned char _a = 0x00;
    unsigned char _b = 0xFF;

    unsigned char a = _a;
    unsigned char b = ~_b;	 // b will be assigned with 0x00 with no problems		

    if (a != b)
    {
        printf("test!/n"); // "test" is not printed out
    }

I ended up doing the following, a casting,  since I needed them all to be unsigned:


    unsigned char _a = 0x00;
    unsigned char _b = 0xFF;

    unsigned char a = _a;
    unsigned char b = _b;			

    if (a != ~(signed char)b)
    {
        printf("test!/n"); // "test" is not printed out
    }

Thanks in advance

This topic has a solution.
Last Edited: Wed. Aug 24, 2016 - 04:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You don't half believe in making things complicated for yourself.
.
You can compare items of the same type without any casts.
.
David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr_ivan wrote:
with a logic similar to the following:

Show a complete minimal test program, in standard C.

 

For example, what is a "uint_8"?  uint8_t ? "unsigned char"?

 

Tell toolchain and version.

 

 

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
You can compare items of the same type without any casts

I know David, but the ~ operator does not make any effect and

 

if (a != ~b)

is evaluated as

if (a != b)

...I am not saying that I think this would happen, this is what happends and the problem only went away until I used the casting...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the reply

 

theusch wrote:
Show a complete minimal test program, in standard C.   For example, what is a "uint_8"?  uint8_t ? "unsigned char"?   Tell toolchain and version

 

I am sorry, what exactly do you mean with a complete minimal test program? 

 

Yes, uint8_t is unsigned char and int8_t is signed char (see here)

 

The toolchain I am using is AVR/GCC native to AtmelStudio 6.2

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want to test a against b, 0x00 is not equal to 0xFF
If you assign ~0xFF to b, b will contain 0x00. And 0x00 is equal to 0x00.
.
You can get anomalies when you compare an int with an unsigned.
Or if you compare an unsigned with a negative literal.
.
Please copy-paste when asking questions. i.e. get your spelling correct.
.
Confession. I am replying on a tablet.
.
David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

...and AVR model.

 

With CodeVision and 6.2 simulator:

 

Aha!  Reproduced OP's result with GCC in Studio 6.2:

 

 

So, C promotion rules?  In CV, I force 8-bit operations in all my AVR8 apps: ("Promote char to int" is unchecked)

 

But changing that compiler option in CV doesn't change my result above.   CV isn't strictly playing by promotion rules?  Or indeed a GCC situation?  You gurus decide.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The example in in your first post is not standard c, you used "uint_8",  my compiler does not recognize that keyword!  

So when I and david above tried your code it did not compile, so he asked for you to post a complete (working) example for us to try out.

Don't post with hand typed code, use cut/paste so we see the actual code that your having a problem with....

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr_ivan wrote:
Yes, uint8_t is unsigned char and int8_t is signed char (see here)

We all know the stdint.h types uint8_t and int8_t but in post#1 you used:

uint_8 _a = 0x00;

that type could be anything - it certainly is not a stdint.h type.

 

if you are just starting out with C then I would suggest it may be easier to use C on a PC rather than adding the extra complication of using it on an AVR. If I do this on a PC:

cliff@ubuntu:~$ cat test.c
#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    uint8_t a = 0x00;
    uint8_t b = 0xFF;

    printf("a = %02X, b=%02X, ~a=%02X, ~b=%02X, a==b=%d, a==~b=%d ~a==b=%d\n", a, b, (uint8_t)~a, (uint8_t)~b, (uint8_t)a==(uint8_t)b, (uint8_t)a==(uint8_t)~b, (uint8_t)~a==(uint8_t)b);
    return 0;
}
cliff@ubuntu:~$ gcc -Os test.c -o test
cliff@ubuntu:~$ ./test
a = 00, b=FF, ~a=FF, ~b=00, a==b=0, a==~b=1 ~a==b=1

But see what happens if I don't cast the results:

cliff@ubuntu:~$ cat test.c
#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    uint8_t a = 0x00;
    uint8_t b = 0xFF;

    printf("a = %02X, b=%02X, ~a=%02X, ~b=%02X, a==b=%d, a==~b=%d ~a==b=%d\n", a, b, ~a, ~b, a==b, a==~b, ~a==b);
    return 0;
}
cliff@ubuntu:~$ ./test
a = 00, b=FF, ~a=FFFFFFFF, ~b=FFFFFF00, a==b=0, a==~b=0 ~a==b=0

EDIT: for learning purposes I think I'd be tempted to introduce new variables:

    uint8_t nota = (uint8_t)~a;
    uint8_t notb = (uint8_t)~b;

then compare a to notb and so on which localises the need to cast the ~ result to one (or rather two) locations.

Last Edited: Wed. Aug 24, 2016 - 03:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I apologize, I just changed it to standard C and yes, I made spelling mistakes trying to write fast an example 

Last Edited: Wed. Aug 24, 2016 - 03:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For fun, I changed the variables to "unsigned int" in GCC.  Then the optimizer reared its head and knew what the result would be.  So I needed "volatile"...

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
in post#1 you used: uint_8 _a = 0x00;

 

 I made spelling mistakes trying to write fast an example. The code is full with other statement that have nothing to do with the question... I learned my lesson, sorry

 

clawson wrote:
But see what happens if I don't cast the results:

 

On your example you used the casting:

 

(uint8_t)a==(uint8_t)~b

 

on variables that were already uint8_t. It also solved my problem but why does this need to be done? Yes, I know is a very basic question. I am not new to C, just bad at it if you will :) ,but I have never noticed this.

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
For fun, I changed the variables to "unsigned int" in GCC.  Then the optimizer reared its head and knew what the result would be.  So I needed "volatile"...

 

Thanks a lot for going through the trouble of testing it on both compilers, theusch (and for the sceenshots).

 

Changing them to unsigned int instead of char also worked with no castings necessary. But I have to be honest, I still don't understand what basic rule of C I am not paying attention to or could this really just be related to AVR/GCC like you asked above?

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr_ivan wrote:
on variables that were already uint8_t.
True dat. I didn't need the cast on the left - was being a bit over eager. The following would have been OK:

a==(uint8_t)~b

The point being that the result of ~ is not uint8_t. I always have to go and look it up but I believe it'll be promoted to "(signed) int" which is why the 0xFFFFFFFF was seen when it was printed - this is of course on a PC where sizeof(int)==4. On an AVR the result was likely 0xFFFF but, still, 0xFFFF != 0xFF which is why the comparison failed.

 

Here's my test code wit slightly less enthusiastic type casting. Still gets the desired result...

cliff@ubuntu:~$ cat test.cpp
#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    uint8_t a = 0x00;
    uint8_t b = 0xFF;

    printf("a = %02X, b=%02X, ~a=%02X, ~b=%02X, a==b=%d, a==~b=%d ~a==b=%d\n", a, b, (uint8_t)~a, (uint8_t)~b, a==b, a==(uint8_t)~b, (uint8_t)~a==b);
    return 0;
}

cliff@ubuntu:~$ ./test
a = 00, b=FF, ~a=FF, ~b=00, a==b=0, a==~b=1 ~a==b=1

The point being that I just had to typecast the result of any ~ operation. As I say I could make life simpler with:

cliff@ubuntu:~$ cat test.cpp
#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    uint8_t a = 0x00;
    uint8_t b = 0xFF;
    uint8_t NOTa = (uint8_t)~a;
    uint8_t NOTb = (uint8_t)~b;

    printf("a = %02X, b=%02X, ~a=%02X, ~b=%02X, a==b=%d, a==~b=%d ~a==b=%d\n", a, b, NOTa, NOTb, a==b, a==NOTb, NOTa==b);
    return 0;
}

cliff@ubuntu:~$ g++ test.cpp -o test -Os
cliff@ubuntu:~$ ./test
a = 00, b=FF, ~a=FF, ~b=00, a==b=0, a==~b=1 ~a==b=1

BTW I switched from test.c to test.cpp here as I thought <typeinfo> and typeid(a).name() and stuff looked very interesting but I ditched the idea because the support in G++ in Linux does not seem as extensive as in MSVC that I was reading about. GNU just outputs mysterious one letter type IDs from the .name() method while apparently MSVC pushes the boat out with fully worded type names. I can't help thinking it could be a good learning tool (for ALL of us!) to explore <typeinfo> in C++ ! It will actually tell you what the type of ~a is without you having to dig out the C manual.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr_ivan wrote:
why does this need to be done?

To dig deeper, it is time to examine the generated code for each case.

 

Then, for the difference, you will [in all probability] find that the promotion rules of C did some widening.

 

If you can decipher the C standard then you will understand.  Many of us have trouble with the deciphering. ;)

http://www.open-std.org/jtc1/sc2...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, I had to try out the "good" version of <typeinfo> in MSVC so I used this:

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

int _tmain(int argc, _TCHAR* argv[])
{

    uint8_t a = 0x00;
    uint8_t b = 0xFF;
    uint8_t NOTa = (uint8_t)~a;
    uint8_t NOTb = (uint8_t)~b;

    printf("a = %02X, b=%02X, ~a=%02X, ~b=%02X, a==b=%d, a==~b=%d ~a==b=%d\n", a, b, NOTa, NOTb, a==b, a==NOTb, NOTa==b);
    printf("type(a)=%s, type(b)=%s, type(~a)=%s\n", typeid(a).name(), typeid(b).name(), typeid(~a).name());
    getchar();
    return 0;
}

and got this:

...\Visual Studio 2010\Projects\itoa\Debug>itoa
a = 00, b=FF, ~a=FF, ~b=00, a==b=0, a==~b=1 ~a==b=1
type(a)=unsigned char, type(b)=unsigned char, type(~a)=int

Don't worry why this is called itoa.exe but the fact is that it confirms the type of ~a and it is NOT unsigned char. And THAT is the issue here.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The point being that the result of ~ is not uint8_t

Thanks a lot for the help! And you were absolutely right. I even found it on the FAQ of AVR/GCC now that I knew the kewword to search... blush

 

Why does the compiler compile an 8-bit operation that uses bitwise operators into a 16-bit operation in assembly?

 

Bitwise operations in Standard C will automatically promote their operands to an int, which is (by default) 16 bits in avr-gcc.

To work around this use typecasts on the operands, including literals, to declare that the values are to be 8 bit operands.

This may be especially important when clearing a bit:

 

var &= ~mask; /* wrong way! */

 

The bitwise "not" operator (~) will also promote the value in mask to an int. To keep it an 8-bit value, typecast before the "not" operator:

 

var &= (unsigned char)~mask;

Last Edited: Wed. Aug 24, 2016 - 05:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you can decipher the C standard then you will understand.  Many of us have trouble with the deciphering. ;)

http://www.open-std.org/jtc1/sc2...

 

Thanks! I will try to decipher it from now on

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I did not know char arguments were promoted in the if() comparison, I learned something today, I guess I should have known that for as long as I have been using C!

thanks to the OP for the question! 

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes,  the ~ operator will promote an expression to int.

 

But if you have assigned the result to a uint8_t it ill be truncated to 8 bits anyway.   I suspect that GCC will notice this and optimise the operations.    Check in the Simulator or read the LSS.

 

So comparing two uint8_t variables will work ok.

 

Untested.   Perhaps I should read this thread more thoroughly.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I say, for anyone who has access to MSVC that <typeinfo> stuff looks like it could be very instructive if you are ever in any doubt as to what the resulting type of an expression might be. 

 

(purists will know every clause of the C standard by heart and will simply know such things!) 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
(purists will know every clause of the C standard by heart and will simply know such things!)

Yeah, and Sheldon Cooper only gets happy once a year on his birthday.  Many of us have lives. 'Freak lives matter.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I also never noticed this promotion of intermediate results. Always learning smiley

 

I made a little experiment with several unary operators (other than ~):

int main(void) {
    char a=1;
    printf("%d\n", sizeof (-a));
    printf("%d\n", sizeof (+a));
    printf("%d\n", sizeof (&a));
    printf("%d\n", sizeof (++a));
    printf("%d\n", sizeof (sizeof a));
    printf("%d\n", sizeof (!a));
}

On a PC, 32 bit x86 target with Pelles C, output is:

4
4
4
1
4
4

64 bit target:

 

4
4
8
1
8
4

So clearly we need to be careful with this stuff. I also noticed that with binary operators promotion also occurs, and also with the ternary operator ?:

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What does MISRA say about this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
clawson wrote:
(purists will know every clause of the C standard by heart and will simply know such things!)

Yeah, and Sheldon Cooper only gets happy once a year on his birthday.  Many of us have lives. 'Freak lives matter.

'Twas Amy's birthday.

Anyone remember International Theophysical Year?
Someone, please answer "Yes."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avr_ivan wrote:
Thanks a lot for the help! And you were absolutely right. I even found it on the FAQ of AVR/GCC now that I knew the kewword to search... blush

 

Why does the compiler compile an 8-bit operation that uses bitwise operators into a 16-bit operation in assembly?

 

Bitwise operations in Standard C will automatically promote their operands to an int, which is (by default) 16 bits in avr-gcc.

To work around this use typecasts on the operands, including literals, to declare that the values are to be 8 bit operands.

This may be especially important when clearing a bit:

 

var &= ~mask; /* wrong way! */

 

The bitwise "not" operator (~) will also promote the value in mask to an int. To keep it an 8-bit value, typecast before the "not" operator:

 

var &= (unsigned char)~mask;

Actually, 'tis the second that is more likely to be a problem.

If var is bigger than a byte, the second will clear bits not specified by mask.

If mask is not negative, the first will work just fine.

If var is 8 bits, both will work just fine.

If var and mask are the same size, the first will work just fine.

 

Anyone remember International Theophysical Year?
Someone, please answer "Yes."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
'Twas Amy's birthday.

Indeed.

"I look forward to your next birthday, when we do it again."

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.