c++coperator-precedencelanguage-design

Why do bit operators have such low precedence in C and C++?


In C and C++, the expression some_num & 0xABCD == 5 will effectively evaluate as some_num & (0xABCD == 5). This is unlike all of the standard arithmetic operators, as they have higher precedence than the comparison operators, so some_num + 5 == 5 will evaluate as we expect.

Why is this? It seems very counterintuitive that the standard arithmetic operators have a much higher precedence than the bitwise arithmetic operators.


Solution

  • This is a historical artifact of the language.

    Early versions of C didn't have the || and && operators, so the | and & operators were used for logical AND and logical OR in cases where the operands had values of either 0 or 1.

    As a result, these operators were given lower precedence to fit into this use case.

    When the || and && operators were later added around 1972, the precedence of | and & remained lower than that of arithmetic and relational operators to avoid breaking existing code, and that decision persists to today.

    From Dennis Ritchie's paper, The Development of the C Language:

    Rapid changes continued after the language had been named, for example the introduction of the && and || operators. In BCPL and B, the evaluation of expressions depends on context: within if and other conditional statements that compare an expression's value with zero, these languages place a special interpretation on the and (&) and or (|) operators. In ordinary contexts, they operate bitwise, but in the B statement

    if (e1 & e2) ...
    

    the compiler must evaluate e1 and if it is non-zero, evaluate e2, and if it too is non-zero, elaborate the statement dependent on the if. The requirement descends recursively on & and | operators within e1 and e2. The short-circuit semantics of the Boolean operators in such `truth-value' context seemed desirable, but the overloading of the operators was difficult to explain and use. At the suggestion of Alan Snyder, I introduced the && and || operators to make the mechanism more explicit.

    Their tardy introduction explains an infelicity of C's precedence rules. In B one writes

    if (a==b & c) ...
    

    to check whether a equals b and c is non-zero; in such a conditional expression it is better that & have lower precedence than ==. In converting from B to C, one wants to replace & by && in such a statement; to make the conversion less painful, we decided to keep the precedence of the & operator the same relative to ==, and merely split the precedence of && slightly from &. Today, it seems that it would have been preferable to move the relative precedences of & and ==, and thereby simplify a common C idiom: to test a masked value against another value, one must write

    if ((a&mask) == b) ...
    

    where the inner parentheses are required but easily forgotten.