Skip to Content
Course content

13: Operators and Expressions

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing code for a colleague who was writing a driver for a piece of industrial hardware. He had a line of code meant to check if a specific status bit was set and if the device was in "Ready" mode. It looked something like this: if (status & 0x01 == 1 && mode == READY). On the surface, it looks perfect. But the hardware was behaving erratically—the condition was almost always false. The culprit? Operator precedence. In C, the equality operator == has higher precedence than the bitwise AND &. He was actually calculating 0x01 == 1 (which is true/1) and then ANDing that with the status. A simple pair of parentheses would have fixed it, but it took us three hours of staring at a logic analyzer to realize the compiler was just doing exactly what he told it to do, not what he meant for it to do.

The Arithmetic Grind and the Integer Trap

You already know how to add and subtract, but C has a few quirks that can trip you up if you're coming from a higher-level language. The most common is integer division. If you divide two integers, like 5 / 2, C doesn't give you 2.5; it gives you 2. It simply chops off the decimal. If you need that precision, at least one of those numbers has to be a float or a double.

Then there is the modulo operator %. I use this constantly. It gives you the remainder of a division. It's the cleanest way to check if a number is even or odd (x % 2 == 0) or to keep a counter wrapping around a maximum value, like a clock. You also have the increment ++ and decrement -- operators. Just be careful with the difference between ++i (prefix) and i++ (postfix). The prefix version increments the value before the expression is evaluated, while the postfix version uses the current value and then increments it. I generally suggest using them on their own line unless you're very comfortable with the distinction; it makes the code much easier for the next person to read.

Logical Flow and Short-Circuiting

When you're building complex logic, you'll rely on && (AND), || (OR), and ! (NOT). The real power here is something called "short-circuit evaluation." If you have if (a == 0 || b == 10) and a is indeed 0, C doesn't even look at b. It already knows the whole expression is true, so it stops right there.

I've used this trick for years to prevent crashes. For example, if you're checking if a pointer is valid before accessing its data, you can write if (ptr != NULL && ptr->value == 5). Because of short-circuiting, if ptr is NULL, the second part of the expression—which would cause a segmentation fault—is never even executed. It's a concise way to bake safety into your logic.

Bitwise Manipulation and Precedence

C is often used close to the metal, which means you need to talk to hardware in bits. This is where the bitwise operators come in: & (AND), | (OR), ^ (XOR), ~ (NOT), and the shifts << (left) and >> (right). Shifting a bit to the left by one is effectively multiplying by two; shifting right is dividing by two.

Returning to my earlier story: precedence is everything here. C has a very specific hierarchy of which operators are handled first. Arithmetic usually beats relational operators, and relational operators beat logical ones. However, bitwise operators often sit in a weird middle ground. My rule of thumb? If an expression uses more than one type of operator, wrap the priority parts in parentheses. It doesn't matter if the compiler doesn't "need" them; it tells the person reading your code exactly what your intention was. I'd rather look at a few extra parentheses than spend a weekend debugging a precedence error.




📋 Practical Task

Exercise: Hardware Status Register Parser

You are writing a small utility to parse a 1-byte status register from a virtual sensor. The register is represented as an unsigned char. You need to extract specific information using bitwise and arithmetic operators.

The Register Layout:

  • Bit 0: Device Active (1 = Active, 0 = Inactive)
  • Bit 1: Error Flag (1 = Error, 0 = OK)
  • Bits 2-4: Mode (A 3-bit value representing the operation mode)
  • Bits 5-7: Battery Level (A 3-bit value where 0 is empty and 7 is full)

Your Task: Write a program that takes a hexadecimal value (e.g., 0x5A) as the register value and prints the following:

  1. Whether the device is Active or Inactive.
  2. Whether there is an Error or if it's OK.
  3. The numerical value of the Mode (you'll need to shift the bits right and mask them).
  4. The Battery Level as a percentage (calculate this by taking the 3-bit value, dividing by 7.0, and multiplying by 100).

Hint: Use the bitwise AND operator (&) with a mask to isolate the bits you need before shifting or calculating.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.