could logical negation have been implemented as bitwise negation in legacy compilers?

3

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

c
asked on Stack Overflow Dec 10, 2018 by aka.nice • edited Dec 10, 2018 by aka.nice

4 Answers

4

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.

answered on Stack Overflow Dec 10, 2018 by John Bollinger
3

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.

answered on Stack Overflow Dec 10, 2018 by aaaaaa123456789
3

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.

answered on Stack Overflow Dec 10, 2018 by Sergey Kalinichenko
0

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.

answered on Stack Overflow Dec 10, 2018 by Brendan

User contributions licensed under CC BY-SA 3.0