Arithmetic Instructions
How add, sub, imul, inc, dec, and neg implement C++ arithmetic.
Doing the Math
Moving data around is only useful if you eventually compute something with it. This lesson covers the basic integer arithmetic instructions and, just as importantly, how the arithmetic you write in C++ maps onto them. Once you can look at a + b and predict the add, you are reading rather than decoding.
The arithmetic instructions follow the same mnemonic dst, src shape as mov, with one twist: most of them are two-operand and in-place. An instruction like add eax, edx means eax = eax + edx. The destination is also one of the inputs and gets overwritten with the result. Keep that in mind and the listings make sense.
Addition: add
Start with the simplest possible case:
int compute(int a, int b)
{
return a + b;
}
At -O0:
compute(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
mov DWORD PTR -8[rbp], esi
mov edx, DWORD PTR -4[rbp]
mov eax, DWORD PTR -8[rbp]
add eax, edx
pop rbp
ret
Skim past the now-familiar plumbing and the one line that does the work is add eax, edx: "eax = eax + edx." The two arguments were loaded into eax and edx, added together with the sum landing in eax, and since eax is the return register the function is done. The + in your C++ became exactly one add.
Subtraction: sub
Subtraction is the mirror image. At -O2, where the plumbing is gone:
int diff(int a, int b)
{
return a - b;
}
diff(int, int):
mov eax, edi
sub eax, esi
ret
The first argument a (in edi) is copied to eax, then sub eax, esi computes eax = eax - esi, i.e. a - b. Order matters here in a way it did not for add: sub subtracts the source from the destination, so the compiler must arrange for the minuend (a) to be in the destination. Watching which operand ends up where is how you confirm you have the direction right.
Multiplication: imul
You already met imul ("integer multiply") in Chapter 1. In its two-operand form it works just like add and sub:
int product(int a, int b)
{
return a * b;
}
product(int, int):
mov eax, edi
imul eax, esi
ret
imul eax, esi computes eax = eax * esi. The i stands for "integer" (specifically signed); there is a separate mul for certain unsigned cases, but for ordinary int and unsigned multiplication you will almost always see imul.
imul has a handy three-operand form too, used when multiplying by a constant:
int scale(int n)
{
return n * 100;
}
scale(int):
imul eax, edi, 100
ret
Read imul eax, edi, 100 as "eax = edi * 100." The three-operand version takes a source register and an immediate and writes the product to a separate destination, so it does not have to overwrite an input. It is the one arithmetic instruction that breaks the in-place pattern, and it exists precisely for the common "multiply a value by a literal" case.
Increment, Decrement, and Negate
Three single-operand instructions round out the basics:
inc dstadds 1:dst = dst + 1.dec dstsubtracts 1:dst = dst - 1.neg dstflips the sign:dst = -dst.
neg shows up reliably for unary minus:
int negate(int n)
{
return -n;
}
negate(int):
mov eax, edi
neg eax
ret
The argument is copied to eax and neg eax negates it. Clean and direct.
inc and dec are interesting because the compiler does not always use them, even for n + 1. Watch:
int bump(int n)
{
return n + 1;
}
At -O0 it does what you would guess:
bump(int):
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
mov eax, DWORD PTR -4[rbp]
add eax, 1
pop rbp
ret
Note that even here it chose add eax, 1 rather than inc eax. And at -O2 it does something different again:
bump(int):
lea eax, 1[rdi]
ret
That lea is not arithmetic in the usual sense at all; it is the address-calculation instruction, repurposed to compute rdi + 1 in a single step without even touching eax as an input. Modern compilers often prefer add and lea over inc/dec for subtle performance reasons on contemporary CPUs. You will still encounter inc and dec in plenty of code, so know what they mean, but do not be surprised when n + 1 compiles to add or lea instead. The whole story of lea is the final lesson of this chapter.
When Several Operations Combine
Real expressions chain these instructions. Here is one that uses three at once:
int evaluate(int a, int b)
{
return a * b - (a + b);
}
At -O2:
evaluate(int, int):
mov eax, edi
add edi, esi
imul eax, esi
sub eax, esi
ret
Trace it carefully and you can see the compiler being clever. a is in edi, b in esi. It copies a into eax, then computes edi = a + b (add edi, esi), then eax = a * b (imul eax, esi)... but wait, the final sub is sub eax, esi, not sub eax, edi. The optimizer rearranged the algebra: it is computing a*b - a - b step by step rather than literally forming (a + b) and subtracting it. The result is identical, but the instruction sequence is the compiler's, not a word-for-word translation of your parentheses. Spotting that the assembly computes the same value by a different route is exactly the kind of reading this course builds toward.
Try It Yourself
The exercise for this lesson has you write a small expression mixing multiplication, addition, and subtraction, then read the assembly panel to find the imul, add, and sub instructions that carry it out. Reading the arithmetic for an expression you wrote yourself is the fastest way to lock in the mapping from +, -, and * to their instructions.
Key Takeaways
- Most arithmetic instructions are two-operand and in-place:
add eax, edxmeanseax = eax + edx. add,sub, andimulimplement C+++,-, and*. Withsub, the source is subtracted from the destination, so operand order matters.imulalso has a three-operand form,imul dst, src, imm, used for multiplying by a constant.inc,dec, andnegare single-operand: add 1, subtract 1, and negate.negis the usual translation of unary minus.- Compilers often render
n + 1asaddor evenlearather thaninc, for performance reasons. - The optimizer may reorder or rewrite the algebra of an expression; the assembly computes the same value, not necessarily by your exact steps.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Arithmetic Instructions - Quiz
Test your understanding of the lesson.
Practice Exercises
Map an Expression to Arithmetic Instructions
Finish the evaluate function so it returns (a * b) - (a + b). The program prints evaluate(6, 7), evaluate(10, 2), and evaluate(4, 4). Once your output is correct, read the assembly panel and find the three arithmetic instructions that carry out the work: imul for the multiplication, add for the sum, and sub for the subtraction.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!