DEC subtracts 1 from a byte or word register or memory operand. Like INC, it has a one-byte encoding for the 16-bit registers, and a memory operand needs BYTE PTR or WORD PTR when its size is not otherwise known: DEC WORD PTR [BX].

Flags

OF, SF, ZF, AF and PF are set as SUB x, 1 would set them; CF is left unchanged. ZF = 1 when the result is 0, which is what counting loops test. SF = 1 when the value has gone below zero (as signed), and OF = 1 when a signed value steps from 80h to 7Fh (or 8000h to 7FFFh). Going from 0 to FFh does not set CF.

Counting down

DEC followed by JNZ is the basic counted loop. It works with any register, not only CX, and the counter can be a byte. The example prints 54321.

MOV CL, 5
AGAIN1:
MOV AL, CL
ADD AL, '0' ; digit -> character
MOV DL, AL
MOV AH, 02h ; DOS: print DL
INT 21h
DEC CL ; ZF = 1 when it reaches 0
JNZ AGAIN1

With the counter in CX, LOOP does the same decrement and jump in one instruction (and does not change any flags at all). DEC CX / JNZ is useful when the loop body needs the flags, or when another register holds the count.

Pitfalls

A loop that starts with the counter at 0 runs 256 (byte) or 65536 (word) times, because the first DEC wraps it to FFh or FFFFh. Check for 0 before entering the loop when that can happen. And since CF is not set on wrap-around, JC after DEC tests a stale flag; use JS or JZ instead.

Later CPUs

As with INC, the one-byte register forms (48h–4Fh) became REX prefixes in 64-bit mode.