4

Are these two lines of code equivalent?

P1->OUT &= ~(uint8_t)(1<<1);

P1->OUT &= (uint8_t)(~(1<<1));
curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • @EugeneSh. I don't see anything that would generate a warning for use on signed `int` values in [**6.5.3.3 Unary arithmetic operators**, paragraph 4](https://port70.net/~nsz/c/c11/n1570.html#6.5.3.3p4): "The result of the ~ operator is the bitwise complement of its (promoted) operand (that is, each bit in the result is set if and only if the corresponding bit in the converted operand is not set). The integer promotions are performed on the operand, and the result has the promoted type. ..." I'm not going to dig into if it's equivalent, though, per the standard. That's language-lawyer stuff. – Andrew Henle Oct 18 '19 at 15:25
  • @AndrewHenle My initial comment (that I removed as I didn't test it) is about the warning about "possible loss of data" when converting signed into into `uint8_t`, which is otherwise perfectly legal – Eugene Sh. Oct 18 '19 at 15:29
  • @EugeneSh. Yeah, gcc is getting a bit overzealous with what are, IMO, [improper warnings](https://stackoverflow.com/questions/57950803/mysterious-type-conversion-warning-when-using-sys-socket-h-macro). So it could very well generate a warning. – Andrew Henle Oct 18 '19 at 15:33

2 Answers2

3

It depends on the type of P1->OUT, and the system. The result of 1 << 1 is of type int.

I am considering the general case of int n; instead of 1 << 1

In

P1->OUT &= ~(uint8_t)(n);

the operand will be widened to int again before the ~ (the integer promotion), and the ~ would be applied to an int. The result will have all the high-order bits 8...k set. If P1->OUT is 8 bits wide it is OK, but if it has more bits then the result is not what you'd expect.

This one is even worse:

P1->OUT &= (uint8_t)(~(n));

The ~ operand will be applied again to an int and that would be converted to uint8_t. now if ~n is actually negative (has its sign bit set) - and in case of ~(1 << 1) it would be negative - it will be fine in two's-complement implementations but totally incorrect in 1's-complement and sign-and-magnitude implementations, because the bit representations would not be the same.


The proper way to do bit twiddling is always use unsigned int or a wider two's complement number:

P1->OUT &= ~(1U << n);

or

P1->OUT &= (~(1U << n)) & 0xFF;

to avoid the arithmetic conversions and integer promotions ever producing signed numbers.

3

TL;DR — No! (They are not always the same, and the result depends on the type of P1->OUT.)

I'm assuming the normal case of 2's-complement arithmetic. If you suffer from sign-magnitude or 1's-complement arithmetic, you have to think quite a lot harder, but the conclusion is probably the same (not always the same).

Case 1:

  • (1<<1) is an int value (0x02).
  • (uint8_t)(1<<1) converts to uint8_t, but the value is still 0x02.
  • ~(uint8_t)(1<<1) converts the uint8_t value to int again (usual arithmetic conversions) and applies the bit-wise inversion operator ~ to the result.

Assuming a 32-bit int type, you get 0xFFFFFFFD to & with P1->OUT:

P1->OUT &= 0xFFFFFFFD;

Case 2:

  • (1<<1) is an int value (0x02).
  • ~(1<<1) is an int value — assuming 32-bit int, the value is 0xFFFFFFFD.
  • (uint8_t)~(1<<1) casts to uint8_t, zeroing the bits in the more significant bytes.

Assuming a 32-bit int type, you get 0x000000FD to & with P1->OUT:

P1->OUT &= 0x000000FD;

So, the value combined with P1->OUT is different, but the effect depends also on the (unspecified) type of P1->OUT. If it is uint8_t, then there's no difference; if it is uint64_t, there is potentially a massive difference, but it also depends on the value in P1->OUT before the compound assignment is executed.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    *"`(uint8_t)~(1<<1)` casts to uint8_t, zeroing the bits in the more significant bytes."* only in 2's complement systems. While most probably all systems that OP will meet will be 2's complement systems, it reminds that the conversion from `int` to `uint8_t` is not just "remove some extra bits" but an actual arithmetic operation. – Antti Haapala -- Слава Україні Oct 18 '19 at 15:42
  • Your case1 analysis is not 100% accurate. `0xFFFFFFFD` is not `int` but `unsigned int` – Eugene Sh. Oct 18 '19 at 15:56
  • @EugeneSh. — if you read really carefully, I didn't actually say that `0xFFFFFFFD` was an `int`; I just said that if your `int` is a 32-bit type, you get the value of `0xFFFFFFFD` — without specifying its type. You're correct about the type of the number, but I didn't say what you're imputing that I said. (Yes: it's an excruciating exegesis of what's written — but it had crossed my mind as I wrote it, and I decided not to complicate my answer more.) What happens when `P1->OUT` is `uint64_t` and `int64_t` is interesting too. – Jonathan Leffler Oct 18 '19 at 15:59
  • @JonathanLeffler Then this piece is missing. You are saying that `~(uint8_t)(1<<1)` is `int`, but replacing it with unsigned int in the final line. I agree that considering every aspect here is probably overcomplicating the answer. – Eugene Sh. Oct 18 '19 at 16:00
  • @EugeneSh.—I'm using the notation 0xFFFFFFFD as the representation of a bit pattern, the type of which is `int`. So, I could add a cast to give `P1->OUT &= (int)0xFFFFFFFD;` but it barely helps, I think. The whole business of using signed types in bitwise operations gets fraught and can easily become UB according to the standard ([§6.3.1.3 Signed and unsigned integers ¶3](http://port70.net/~nsz/c/c11/n1570.html#6.3.1.3): _Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised._ Etc. – Jonathan Leffler Oct 18 '19 at 16:07