0

Set means flag value = 1 and Unset means flag value = 0

Now I understand there are several ways to set and unset flags in MASM, as follows:

test al,0 ; set Zero flag
and al,0 ; set Zero flag
or al,1 ; clear Zero flag

Same goes for Sign flag:

or al,80h ; set Sign flag
and al,7Fh ; clear Sign flag

To set the Carry flag, we use the STC instruction; to clear the Carry flag, we use CLC:

stc ; set Carry flag
clc ; clear Carry flag

To set the Overflow flag, we add two positive values that produce a negative sum. To clear the Overflow flag, we OR an operand with 0:

mov al,7Fh ; AL = +127
inc al ; AL = 80h (-128), OF=1
or eax,0 ; clear Overflow flag

The Overflow and Carry flags operations are self-understood and easy to grasp, but I find it hard to understand the mathematics behind setting the Zero/Sign flags. Any help is appreciated!

Thanks!

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Jishan
  • 1,654
  • 4
  • 28
  • 62
  • 1
    Perhaps considering how the flags are used will help. Consider: You seldom need to *say* that you have overflowed. You do your work, then (as needed) you check for overflow. Intentionally setting the flag is... uncommon. However, the carry flag is often used for purposes beyond the simply arithmetic. For example [DOS](http://spike.scu.edu.au/~barry/interrupts.html#ah3d) frequently used CF to indicate error conditions. – David Wohlferd Jul 05 '18 at 04:52
  • 1
    mind that in most of your samples, setting the flags is "only" a side effect of the operation, and not meant for setting/clearing the flags. (only `stc` and `clc` are explicitly for setting/clearing flags). – Tommylee2k Jul 05 '18 at 06:39

2 Answers2

5

The whole point of using the flags is that they are a side effect of other operations - and you can test the result after the operation has occurred.

So, for example, you can count down from 10 to 0 without explicitly testing for zero:

       mov cx, 10
Again:
       ; Do some stuff, not changing cx
       dec cx
       jnz Again ; Go back to Again if not zero

The "Do some stuff" happens ten times, because dec affects the Z flag.

The reason that stc and clc exist is to help with multi-digit arithmetic. The C flag is used to keep track of "Carries" out of previous arithmetic, so that you can factor them into future operations:

op1 dd  0x12345678 ; 32-bit value
op2 dd  0x9abcdef0 ; 32-bit value

    mov ax,[op1+0] ; Get low word of op1
    mov dx,[op1+2] ; Get high word of op1

    add ax,[op2+0] ; Add in low word of op2
    adc dx,[op2+2] ; Add in high word of op2 - WITH CARRY!

Because of these kinds of operations, you may need to pre-load C with 0 or 1 before starting on the algorithm. Hence clc and stc - and why no other (arithmetic) flags have a "set" or "clear" op-code.

Note that there are other, non-arithmetic flags:

  • The D (direction) flag controls the direction of string instructions such as STOS and MOVS. Thus there are cld and std instructions.
  • The I (interrupt) flag controls whether interrupts are enabled or not. Thus there are cli and sti instructions.
Fifoernik
  • 9,779
  • 1
  • 21
  • 27
John Burger
  • 3,662
  • 1
  • 13
  • 23
4

SF and ZF are set based only on the result, not the inputs.

SF is the highest bit of the result, so (for a 2's complement interpretation of the bit-pattern), it means the result is negative.

SF = ((signed)result < 0);

ZF = (result == 0);

You could also say that ZF is the horizontal OR of all the bits, inverted. (It's cleared if there's a single set bit).

Of course result is 8, 16, 32, or 64 bits, depending on the operand size. The flags are set based on the high bit and zero-ness of the actual output, not the full register that it's part of, for an instruction like neg al.


Of course not all instructions set all flags, for example inc/dec famously leave CF unmodified while setting the others in the usual way, making it usable inside adc loops.

Rotates only set CF, and (for the implicit shift-by-1 opcode) also OF. SF/ZF/PF remain unmodified, unlike with regular non-circular shifts. Unless the shift count was zero, in which case all the flags are unmodified. This is problematic for out-of-order execution of shifts and rotates (flags are an extra dependency for variable-count shifts/rotates), which is why BMI2 shlx/shrx are faster on Intel. And rol/ror are extra uops.


BSF / BSR set ZF based on the input (not the result), and leave the output register unmodified if the input was zero.

Intel's docs say "undefined" in that case, but AMD documents the unmodified behaviour that all hardware actually implements. I think it's unlikely that Intel will ever build hardware that doesn't implement the unmodified behaviour, especially now that BMI1 lzcnt/tzcnt give us an alternative without the false dependency. IDK why they don't just document how bsf/bsr work on their hardware.

Fifoernik
  • 9,779
  • 1
  • 21
  • 27
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847