0

I understand the use of the carry and overflow flags exist for error checking in arithmetic, but why is the zero flag useful to the system/programmer?

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 2
    To compare for equality (sub/cmp), or in general to detect ==0 / !=0 cheaply without needing a compare. – Peter Cordes Sep 18 '22 at 02:36
  • 2
    You tell us: how would you implement `if (x == y)` without it? Likewise, the carry and overflow flags are not used primarily for "error checking", but to implement `if (x < y)` for unsigned and signed data respectively. – Nate Eldredge Sep 18 '22 at 02:36
  • 1
    All the CMP instruction does is set those flags, then all conditional jump instructions jump (or not) based on the flags (equality, etc). – Jens Sep 18 '22 at 02:40
  • @NateEldredge You can implement `x == y` by first computing `x - y` and then checking if the carry flag is set (or clear, depending on convention) if you decrement the result. Easier to just have a zero flag though. – fuz Sep 18 '22 at 02:43
  • 1
    compare with zero is the simplest form of comparison, just check if any bit is set for (in)equality, or check the sign bit for less/greater than. Therefore [even platforms without a flag usually don't have comparison between 2 values directly and just have *compare with 0* instead](https://stackoverflow.com/a/19924300/995714) – phuclv Sep 18 '22 at 02:50
  • So to my understanding it's not because it's more efficient it's just more convenient? You still have to get the two operands and store the result in a third register I think. – Bryan Turns Sep 18 '22 at 02:50
  • 1
    it's more efficient. And convenient. Without a zero flag most comparisons would be more inefficient for both HW and SW. No need to store anything, who tells you a 3rd register is needed? `cmp` and `test` don't store their non-flag results anywhere – phuclv Sep 18 '22 at 02:54
  • @NateEldredge the carry flag is mainly for implementing multi-word arithmetic (that's what `carry` means after all) although it has far more other uses – phuclv Sep 18 '22 at 02:55
  • @phuclv, the carry flag is used for comparisons in nearly every piece of x86 software ever written; only a tiny fraction of that code uses multi-precision arithmetic. – prl Sep 18 '22 at 03:17
  • @prl yes but only "indirectly" via Jcc. The users don't call `JC` or `JNC` ever, unlike `ADC` or `SBC` – phuclv Sep 18 '22 at 03:50
  • @phuclv is the flag not stored in a register? – Bryan Turns Sep 18 '22 at 04:13
  • @BryanTurns "get the two operands and store the result in a third register" means something like 3-operand instructions: `add dest, src1, src2`, i.e. the result is store in the 3rd register. That says nothing about the flag register. And you're almost wrong, modern CPUs macrofuse `cmp`/`test` with `jcc` so the flags may not even need to be updated/stored. And why care about that? Calculating flags is simple enough that it can be efficiently done every cycle. Even with OoO execution CPU manufacturers know how to do that efficiently – phuclv Sep 18 '22 at 04:20
  • @phuclv: `cmp` is a 3-operand instruction: two read-only inputs, and FLAGS as the implicit output. Also, even macro-fused `cmp eax, ecx` / `jne` still does write FLAGS. But yes, the CPU can check branch prediction based directly on the compare result without having to read back that FLAGS result for that purpose. – Peter Cordes Sep 18 '22 at 04:26
  • 1
    But yeah, without ZF, you'd need 3 instructions and a spare general-purpose register, like `mov ecx, src1` / `xor ecx, src2` / `jrcxz equal`. Or `sub src1, src2` / `sub src1, 1` / `jc equal` as @fuz suggested, if you don't mind destroying one input. These are more instructions and obviously less convenient than having FLAGS as a temporary for. Plus as Nate points out it allows conditions like `jge` or `jbe` for signed `>=` or unsigned `<=` for example, not just `>` or `<`. – Peter Cordes Sep 18 '22 at 04:28
  • if the flag isn't hidden then you have to use 2 instructions like in non-flag RISC architectures for the comparison. In any case, a *comparison with 0* result is required for efficient hardware implementation – phuclv Sep 18 '22 at 04:43

2 Answers2

5

An extremely common task for any computer is to compare two integers (or pointers) for being greater / less / equal, and conditionally jump according to the result. This is how you implement higher-level constructs like if (x < y), if (x == y), and so on.

On x86, as in many other architectures, this task is broken into two instructions: CMP or some similar instruction to perform the arithmetic of the comparison, and the conditional Jcc instructions to perform the jump. There has to be some kind of state in the CPU for CMP to communicate its result for Jcc to use, and that's what the FLAGS register is for.

A test for equality is the most basic of these, so it makes sense that there should be a flag which is set according to whether the two operands of CMP were equal. Since CMP is really a subtraction operation under the hood, this is equivalent to asking whether the result of the subtraction was zero, which is easily done in hardware by OR'ing together all the bits and inverting the one-bit result. And that's the zero flag. Since it costs basically nothing, the 8086 designers decided to set the zero flag in this way after almost every arithmetic or logical instruction, not only CMP.

Then "jump if equal" is equivalent to "jump if zero flag set". Indeed, JE and JZ are just different assembly mnemonics for the same machine-code instruction.

The other standard arithmetic flags (carry, sign, overflow) are also used in this way. This is by far a more common use for them than any kind of error checking. (Indeed, many compiled languages do no such error checking and simply ignore overflow if it occurs.) For instance, if you want to jump if AX < BX, where the values in the registers are to be treated as unsigned, you would subtract BX from AX and jump if a borrow occurred, which on x86 sets the carry flag. Then your code simply looks like CMP AX, BX / JC label. The assembler lets you use JB instead of JC so that you can think of it as "jump if below". You can also combine it with the zero flag; if you want to jump if AX <= BX, then you write CMP AX, BX ; JBE label, where JBE will jump if either of the carry or zero flags is set.

The sign and overflow flags are used similarly for signed compares, though the rules are a little more complicated.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
0

The flags are far more useful than just coming out of CMP. Think of code like: "if(!--x) {...}"

This could easily be implemented with a DEC/JZ pair without any need of an intervening CMP.

SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23