I just found legacy code which tests a flag like this:
if( some_state & SOME_FLAG )
So far, so good!
But further in code, I see an improper negation
if( ! some_state & SOME_FLAG )
My understanding is that it is interpreted as (! some_state) & SOME_FLAG
which is probably a bug, and gcc logically barks with -Wlogical-not-parentheses
...
Though it could eventually have worked in the past if ever !some_state
was implemented as ~some_state
by some legacy compiler. Does anyone know if it was possibly the case?
EDIT
sme_state
is declared as int (presumably 32 bits, 2 complement on target achitecture).
SOME_FLAG
is a constant set to a single bit 0x00040000, so SOME_FLAG & 1 == 0
Logical negation and bitwise negation have never been equivalent. No conforming compiler could have implemented one as the other. For example, the bitwise negation of 1 is not 0, so ~1 != !1
.
It is true that the expression ! some_state & SOME_FLAG
is equivalent to (! some_state) & SOME_FLAG
because logical negation has higher precedence than bitwise and. That is indeed suspicious, but the original code is not necessarily in error. In any case, it is more likely that the program is buggy in this regard than that any C implementation evaluated the original expression differently than the current standard requires, even prior to standardization.
Since the expressions (! some_state) & SOME_FLAG
and !(some_state & SOME_FLAG)
will sometimes evaluate to the same value -- especially if SOME_FLAG
happens to expand to 1
-- it is also possible that even though they are inequivalent, their differences do not manifest during actual execution of the program.
While there was no standard before 1989, and thus compilers could do things as they wished, no compiler to my knowledge has ever done this; changing the meaning of operators wouldn't be a smart call if you want people to use your compiler.
There's very little reason to write an expression like (!foo & FLAG_BAR)
; the result is just !foo
if FLAG_BAR
is odd or always zero if it is even. What you've found is almost certainly just a bug.
It would not be possible for a legacy compiler to implement !
as bitwise negation, because such approach would produce incorrect results in situations when the value being negated is outside the {0, 0xFF...FF} set.
Standard requires the result of !x
to produce zero for any non-zero value of x
. Hence, applying !
to, say, 1 would yield 0xFF..FFFE, which is non-zero.
The only situation when the legacy code would have worked as intended is when SOME_FLAG
is set to 1
.
Let's start with the most interesting (and least obvious) part: gcc logically barks with -Wlogical-not-parentheses
. What does this mean?
C has two different operators that have similar looking characters (but different behaviour and intended for very different purposes) - the &
which is a bitwise AND, and &&
which is a boolean AND. Unfortunately this led to typos, in the same way that typing =
when you meant ==
can cause problems, so some compilers (GCC) decided to warn people about "&
without parenthesis used as a condition" (even though it's perfectly legal) to reduce the risk of typos.
Now...
You're showing code that uses &
(and not showing code that uses &&
). This implies that some_state
is not a boolean and is number. More specifically it implies that each bit in some_state
may be completely independent and unrelated.
For an example of this, let's pretend that we're implementing a Pacman game and need a nice compact way to store the map for each level. We decide that each tile in the map might be a wall or not, might be a collected dot or not, might be power pill or not, and might be a cherry or not. Someone suggests that this can be an array of bytes, like this (assuming the map is 30 tiles wide and 20 tiles high):
#define IS_WALL 0x01
#define HAS_DOT 0x02
#define HAS_POWER_PILL 0x04
#define HAS_CHERRY 0x08
uint8_t level1_map[20][30] = { ..... };
If we want to know if a tile happens to be safe to move into (no wall) we could do this:
if( level1_map[y][x] & IS_WALL == 0) {
For the opposite, if we want to know if a tile is a wall we could do any of these:
if( level1_map[y][x] & IS_WALL != 0) {
if( !level1_map[y][x] & IS_WALL == 0) {
if( level1_map[y][x] & IS_WALL == IS_WALL) {
..because it makes no difference which one it is.
Of course (to avoid the risk of typos) GCC might (or might not) warn about some of these.
User contributions licensed under CC BY-SA 3.0