Lecture 3 - Operators, Binary & Hex, Bit Manipulation¶
Target budget for a 150-minute session. Strings are a stretch at the end
(segment 9); if time is short they move to the top of Lecture 4, just before HW1
(see strings_primer.md). Segments 7-8 (the two coding
exercises) are the designated overflow.
0. Recap of Week 1¶
- Last week, in brief: the edit -> compile -> run loop with
clang; static typing (int,char,float,double);if/else, the three loops, andswitch;printf/scanf(remember the&onscanf); and splitting code into.c/.hfiles built with aMakefile. - At the end of Lecture 2 we promised logical operators "in detail next week"
- that is where we start today. By the end of today we will know how numbers are actually stored as bits, and how to read and manipulate those bits directly.
1. Operators in C¶
We have already been using operators (+, <, =) without naming them. Let us
make the full set explicit. We hold off on the bitwise operators until we have
covered binary numbers - they only make sense once you can see the bits.
Arithmetic (quick - mostly review)¶
+ - * / %- Integer division truncates toward zero:
7 / 2is3, not3.5. To get3.5at least one operand must be floating point:7.0 / 2. %is the remainder (modulo):7 % 2is1. Defined for integers only. Handy idioms:n % 2 == 0tests even;n % 10peels off the last decimal digit.
Comparison (relational)¶
== != < > <= >=- Every comparison produces an
int:1for true,0for false. This is whyif (x)works for any integer - there is no separate boolean type in play.
Logical (the promised topic)¶
&&(and),||(or),!(not). Operands are treated as truthy/falsy:0is false, anything non-zero is true.- Short-circuit evaluation is the key idea:
a && b- ifais false,bis never evaluated (the answer is already false).a || b- ifais true,bis never evaluated.- This is not just an optimization - it is a tool. The classic guard:
If
nis0, the&&stops before the division ever happens - no divide-by-zero. Order matters: put the test that protects the second operand first.
Assignment and compound assignment¶
- Plain
=stores a value. The compound forms are shorthand:x += 5meansx = x + 5; likewise-= *= /= %=. - Increment / decrement:
i++andi--. Pre- vs post- matters when used inside a larger expression:a = i++stores the oldithen increments;a = ++iincrements first. On its own line they are identical - prefer that for clarity.
Two traps to put on the board¶
=vs==.if (x = 5)does not compare - it assigns5toxand then tests5, which is always true. The compiler will warn; do not ignore it.- Precedence.
*and/bind tighter than+and-;&&binds tighter than||. When in doubt, parenthesize -(a && b) || creads better than trusting the table.
2. Why binary? Electricity, bits, and bytes¶
- A computer is built from transistors - tiny switches that are either on or off. Two clean, distinguishable states (high voltage / low voltage) are far more reliable to build and detect than ten. So the machine counts in base 2.
- A bit is one binary digit:
0or1. That is the whole alphabet. - Bits are grouped:
- nibble = 4 bits
- byte = 8 bits - the standard unit of storage
- One byte has
2^8 = 256possible patterns, so it holds the values 0 to 255 (unsigned). Anintis typically 4 bytes = 32 bits. - Everything is ultimately bits - integers, characters, images, this lecture. Today we focus on how integers are laid out, because that is what bit manipulation acts on.
3. Binary and hexadecimal number systems¶
Place value, generalized¶
- Decimal is base 10:
347 = 3*100 + 4*10 + 7*1 = 3*10^2 + 4*10^1 + 7*10^0. - Binary is base 2, same idea with powers of 2:
1011(binary)= 8 + 0 + 2 + 1 = 11(decimal). - Binary -> decimal: add up the place values where a
1sits. - Decimal -> binary: repeatedly subtract the largest power of 2 that fits (or repeatedly divide by 2 and read the remainders bottom-up). Do one of each on the board.
Worked examples¶
- Binary -> decimal (
1101 1010): add the place values where a1sits. - Decimal -> binary, subtract-powers (
37): take the largest power of 2 that fits, subtract, repeat. - Decimal -> binary, divide-by-2 (
13): read remainders bottom-up.
Hexadecimal, and why we bother¶
- Hex is base 16: digits
0-9thenA-F(A=10 ...F=15). - The payoff: one hex digit is exactly four bits (a nibble), because
16 = 2^4. That makes hex a compact, human-readable shorthand for binary. - Binary <-> hex by grouping: split the bits into groups of 4 (from the right) and translate each group independently. No arithmetic across groups - this is why programmers reach for hex constantly.
-
In C source, hex literals are written with a
0xprefix:0xFFis255. -
Decimal -> hex (
250): convert to binary, then group into nibbles.
Binary arithmetic (brief)¶
- Addition works just like decimal, but you carry at 2 instead of 10:
-
Note what happens when a carry runs off the left end of a fixed-width type: that is overflow - the result wraps. We will see this concretely with shifts.
-
Subtractions, multiplications, and divisions also work as expected, but we will not do them by hand. The key point is that the same rules apply to all bases - the base is just a lens for us humans to read the bits.
In-class exercise break - conversions worksheet¶
Handout: Part A, Exercises A1-A3 - pen and paper, no computer. Work in pairs.
- Convert a handful of values decimal -> binary -> hex and back.
- A couple of binary additions, including one that overflows 8 bits so the table conversation has a concrete example.
- Solutions on the back of the handout; we review a few on the board.
4. Binary logic and truth tables¶
Before the C operators, the logic itself. Each works bit by bit:
- AND (
&):1only if both bits are1. - OR (
|):1if either bit is1. - XOR (
^):1if the bits differ. - NOT (
~): flips every bit.
A B | A&B A|B A^B A | ~A
----+------------- - ---+----
0 0 | 0 0 0 0 | 1
0 1 | 0 1 1 1 | 0
1 0 | 0 1 1
1 1 | 1 1 0
- Do not confuse these with
&&/||. Logical&&/||collapse their whole operands to a single true/false and short-circuit. Bitwise&/|operate on all bits in parallel and always evaluate both sides.1 & 2is0(no bit in common);1 && 2is1(both non-zero).
5. Why bother? Where bit-level programming earns its keep¶
Before the C syntax, the motivation. Manipulating individual bits can feel fiddly, so it is worth seeing why a systems programmer reaches for it constantly. The recurring theme: bits let you say a lot in very little space, very fast, right where the hardware lives.
- Packing many yes/no answers into one number (flags). Instead of eight
separate
boolvariables, store eight on/off settings in the eight bits of one byte. This is how real C APIs take options: you OR named constants together, e.g.open(path, O_WRONLY | O_CREAT | O_APPEND). Each name is a single set bit; the function pulls them apart with masks. - Unix file permissions are bits you already use.
chmod 0755is octal for the permission bitsrwxr-xr-x. Read = 4, write = 2, execute = 1 are just bit positions, and4|2|1 = 7. You have been doing bit math from the shell already. - Talking to hardware and the network. Device registers, sensor status
lines, and packet headers cram several fields into a few bytes. An IPv4 header,
an RGBA color (
0xRRGGBBAA), a subnet mask - all are read by shifting and masking out the field you want. Extract the red channel with(color >> 16) & 0xFF. - Speed. Bitwise ops are about the cheapest thing a CPU does.
x << 1is a multiply by 2;x >> 3divides by 8;x & (N-1)is the remainder mod a power of two - all far faster than the general*,/,%. Compilers lean on this, and so do hot loops. - Memory efficiency (bitsets). Need to track which of 1,000,000 items are "seen"? One bit each fits in 125 KB instead of a million bytes. A bitset or bitmap is just an array of integers used as a wall of flags.
- The building blocks of bigger systems. Hashing, checksums, compression, and cryptography are built largely from XOR and shifts. Even if you never write those, you will read code that does.
The takeaway: bit manipulation is not an academic trick - it is the everyday vocabulary of systems, drivers, networking, and graphics, which is exactly what this course is about. Now the C operators that make it happen.
6. Bit manipulation in C¶
The bitwise operators¶
&AND,|OR,^XOR,~NOT - exactly the truth tables above, applied to every bit of the operands at once.
Shifts¶
- Left shift
x << n: move bits left byn, filling with0on the right. Each shift multiplies by 2:1 << 0 = 1,1 << 1 = 2,1 << 3 = 8. - Right shift
x >> n: move bits right byn. For unsigned values this divides by 2 (flooring):40 >> 1 = 20. - Caution: right-shifting negative signed values is implementation-defined in
spirit; keep bit-twiddling to
unsignedtypes. Preferunsigned intwhen you mean "a bag of bits." 1 << ngives you a value with only bitnset - the building block for everything that follows.
7. Printing hex and binary in C¶
- Hex is built in:
%x(lowercase),%X(uppercase), and%#xto include the0xprefix.%oprints octal. There is also%dfor decimal as always. - There is no portable
%bfor binary. You print binary by hand, walking bits from the most significant down and masking off one at a time: (v >> i) & 1slides bitidown to the bottom and keeps only that bit. Thisshift-then-maskis the idiom for inspecting a single bit - it returns next section.
An aside: The nested if in the code above is a bit verbose. You can also write it as a single line
using the nested conditional operator/ternary operator in C:
In-class exercise break - print binary and hex¶
Handout: Exercise B1 - on the computer. Solution in the separate key.
- Read an integer from the user. Print it in decimal, hex, and binary.
- Reuse
print_binaryfrom above for the binary line;%Xhandles the hex. - Stretch: only print the bits from the highest set bit down, so small numbers do not show 32 leading zeros.
8. Bit masks - set, clear, toggle, test¶
A mask is a value crafted to touch exactly the bits you care about. With
1 << n as the building block, the four standard moves (put these on the board):
unsigned int flags = 0;
flags |= (1 << n); /* SET bit n -> OR with a 1 in that spot */
flags &= ~(1 << n); /* CLEAR bit n -> AND with a 0 in that spot */
flags ^= (1 << n); /* TOGGLE bit n -> XOR flips it */
int on = (flags >> n) & 1; /* TEST bit n -> 1 if set, else 0 */
- Why each works, in one line each:
- OR with 1 forces a bit on; OR with 0 leaves a bit unchanged -> set.
- AND with 0 forces a bit off; AND with 1 leaves it unchanged -> the
~(1 << n)mask is all 1s except a 0 atn-> clear. - XOR with 1 flips; XOR with 0 leaves unchanged -> toggle.
- This is how a single
intcan pack many on/off flags - exactly how real APIs pass options (file permissions, event masks, and so on).
In-class exercise break - bit masks¶
Handout: Exercise B2 - on the computer. Solution in the separate key.
- Start from
flags = 0. Set bits 1 and 3, print (binary), clear bit 1, toggle bit 0, print again at each step and check against what you predict. - Write a small helper
int is_set(unsigned int x, int n)using the shift-and-mask idiom, and use it to report which bits are on.
9. Wrap-up¶
- Operators: arithmetic/comparison/logical/assignment; logical operators
short-circuit, and
=is not==. - Numbers are bits: base 2 because transistors are on/off; hex is base 16 and maps 1 digit <-> 4 bits, which is why we use it.
- Bitwise (
& | ^ ~) act on all bits at once - different from&& ||. Shifts multiply/divide by powers of 2 and build masks via1 << n. - The four mask moves -
|=set,&= ~clear,^=toggle,>> & 1test - are worth memorizing.
10. Stretch (only if time allows, ~20 min) - a first look at strings¶
- If the room is with us and time remains, begin the C strings segment to get a head start on HW1. Otherwise this opens Lecture 4.
- Full material is in
strings_primer.md: a string is achararray ending in'\0', you loop until the terminator, andchars are just small integers (ASCII) - which connects straight back to today's "everything is numbers" theme.