Operators in Assembly
Learn arithmetic, bitwise, and comparison operators in x86 Assembly using NASM, with hands-on Docker-ready examples
In high-level languages, operators like +, *, or && look like a single thing. In x86 assembly, every operator is an explicit CPU instruction acting on registers or memory, and most of them quietly update the processor’s flags register as a side effect. Those flags — zero, carry, sign, overflow — are what later conditional jumps read to decide where to go next.
Because assembly is untyped, the same value can be interpreted as a signed integer, an unsigned integer, an address, or a bit pattern depending on which instruction you choose. MUL and IMUL use the same bits but treat them differently; JL and JB both branch on “less than” but read different flags. Picking the right instruction is the operator.
This tutorial walks through three families of operators using x86 (32-bit) NASM syntax and Linux syscalls: arithmetic (ADD, SUB, MUL, DIV), bitwise and shifts (AND, OR, XOR, NOT, SHL, SHR), and comparison plus conditional jumps (CMP with JE/JNE/JG/JL). Each example keeps results small enough to print as a single ASCII digit, so we can focus on the operators rather than number formatting.
Arithmetic Operators
Arithmetic in x86 lives in instructions, not infix syntax. ADD dst, src performs dst = dst + src. MUL is one-operand and implicitly uses AL/AX/EAX as the other factor and destination. DIV is even more particular: the dividend lives in AX (for byte division), the divisor is the explicit operand, and the quotient lands in AL with the remainder in AH.
Create a file named arithmetic.asm:
| |
A few things to notice:
ADD,SUB, and friends are two-operand:add al, 3meansal = al + 3. The first operand is both source and destination.MUL blis one-operand. The other factor is implicitlyAL, and the 16-bit product lands inAX(high byte inAH, low byte inAL).DIV bldivides the implicit dividend inAXbyBL, leaving the quotient inALand the remainder inAH. There is no single “modulo” instruction — you get the remainder for free fromDIV.- Adding
'0'(ASCII 48) to a single-digit value is the smallest possible “integer to string” conversion.
Bitwise and Shift Operators
Below arithmetic sits the layer assembly was made for: manipulating individual bits. AND, OR, XOR, and NOT operate bitwise across all bits of the destination. Shifts (SHL, SHR) move bits left or right and are the assembly-level equivalent of multiplying or dividing by powers of two.
Create a file named bitwise.asm:
| |
XOR is worth special attention. The idiom xor reg, reg zeros a register more compactly than mov reg, 0 — the Hello World example already used xor ebx, ebx for exactly this reason. SHL by n is identical to multiplication by 2^n for unsigned values, and SHR is the unsigned counterpart of integer division by a power of two; SAR (arithmetic shift right) preserves the sign bit when you need signed semantics.
Comparison and Conditional Jumps
Comparison in assembly is a two-step process: a CMP instruction sets flags, and a conditional jump reads those flags. CMP a, b does the same arithmetic as SUB a, b but throws away the result — it only keeps the flags. The flags then drive a family of jump instructions:
| Mnemonic | Branch when | Reads flag(s) |
|---|---|---|
JE / JZ | equal / zero | ZF=1 |
JNE / JNZ | not equal | ZF=0 |
JG | greater (signed) | ZF=0 and SF=OF |
JL | less (signed) | SF≠OF |
JA | above (unsigned) | CF=0 and ZF=0 |
JB | below (unsigned) | CF=1 |
Create a file named compare.asm:
| |
Notice the inverted logic: we test JLE to skip the “greater than” message, because falling through means the comparison held. This pattern — “test the negation, jump past the body” — is how if blocks are typically lowered to assembly by compilers.
Running with Docker
Each .asm file is assembled, linked, and executed in one step by the image:
| |
Expected Output
Running arithmetic.asm:
5 + 3 = 8
9 - 4 = 5
3 * 2 = 6
8 / 3 = 2 r 2
Running bitwise.asm:
5 AND 3 = 1
5 OR 3 = 7
5 XOR 3 = 6
1 << 3 = 8
8 >> 2 = 2
Running compare.asm:
7 == 7 -> equal
9 > 4 -> greater
2 < 5 -> less
Key Concepts
- Operators are instructions. Every arithmetic, bitwise, or comparison “operator” is a named CPU instruction (
ADD,XOR,CMP, …) that consumes register or memory operands rather than infix syntax. - Two-operand form. Most instructions are
op dst, src, where the destination is also the first source:add eax, ebxmeanseax = eax + ebx. MULandDIVare special. They use implicitAL/AX/EAXoperands and produce wider results, leaving the remainder inAHfor byte division — there is no separate modulo instruction.- Signed vs unsigned is per-instruction. Use
MUL/DIVandJA/JBfor unsigned values,IMUL/IDIVandJG/JLfor signed. The bits are identical; the interpretation comes from which mnemonic you choose. - Flags are the bridge to control flow.
CMP a, bupdates ZF, SF, CF, and OF without storing a result; the following conditional jump reads those flags. This is howifbecomes assembly. xor reg, regis the idiomatic zero. It’s shorter thanmov reg, 0and is recognized by the CPU as a register-clearing pattern.- Shifts are powers of two.
SHL x, nisx * 2^nandSHR x, nis unsignedx / 2^n— much faster thanMUL/DIVwhen you can use them.
Running Today
All examples can be run using Docker:
docker pull esolang/x86asm-nasm:latest
Comments
Loading comments...
Leave a Comment