8

I have found this line in an old 16-bit DOS application (likely to have been generated by Borland Turbo C compiler) and I want to understand the purpose of it:

mov     dx, [bp+var_192]
mov     ax, [bp+var_194]
add     ax, 0FFFCh
adc     dx, 0FFFFh ; <-- here
push    dx
push    ax
push    ds
...               ; more pushes
call    SomeFunction

As far as I understand, the purpose of this "add with carry" instruction (in this particular case) is to set the carry flag to 1, but for whatever reason, it is not used. Is it a compiler optimization to set the carry flag to 1?

What I also don't understand is that dx register is not affected by this operation under debugger (e.g.: if it's equal to zero, it stays zero). I expected dx register to be decreased by 1 (since 0xFFFF is equal to −1).

16
  • 12
    add with carry really means add with carry bit, so it adds the two addends AND the value of the carry flag. So you can add 32-bit integers with just two operations (and easily you can have 64-bit and more) Commented Apr 9 at 16:10
  • 2
    I see. I assumed it means "add and set carry" but it's "add both operands and carry"
    – tigrou
    Commented Apr 9 at 16:15
  • 6
    Yeah; in net FFFFFFFC is being added to the 32-bit quantity dx:ax.
    – Tommy
    Commented Apr 9 at 16:29
  • 1
    @davolfman: It's far more common on the 8086 to use an ADD/ADC pair to perform 32-bit arithmetic than to chain more ADC operations to perform longer arithmetic.
    – supercat
    Commented Apr 9 at 20:47
  • 2
    @Tommy: Which is the same as subtracting 4 from dx:ax.
    – DrSheldon
    Commented Apr 11 at 1:25

2 Answers 2

20

As others have explained, ADC adds its two operands and the carry bit, storing the result in the first operand. Like ADD, it sets the carry bit as appropriate: adding two n-bit values produces a (n+1)-bit result, and the (n+1)th bit goes in the carry flag (counting from the least-significant bit).

So

add     ax, 0FFFCh
adc     dx, 0FFFFh

adds 0xFFFC to the value in AX, storing the result in AX and the carry bit, then adds 0xFFFF plus the carry bit to DX, storing the result in DX and the carry bit.

This allows additions to be performed on integers of arbitrary lengths. Along those lines, the two instructions above add 0xFFFFFFFC to the 32-bit value stored in DX:AX. While addition and subtraction don’t enforce this representation, it is common on x86; 16-bit multiplication stores its result in DX:AX, and 16-bit division applies to the dividend stored in DX:AX.

What you’re seeing in the debugger is essentially the result of adding 0x10000 (0xFFFF + 1) to DX: DX is unchanged, but the carry flag is set.

4
  • 5
    I guess the OP wonders why there are two add instructions. With just one (which must be ADC), one would need always and additional instruction to clear the carry first (CLC/ADC). with the addition of ADD, additions can start with ADD without clearing, continueing with ADC in case of multi word operation. So ADD is essentially CLC+ADC, while ADC is just the basic addition.
    – Raffzahn
    Commented Apr 9 at 19:12
  • 2
    The 6502 has ADC and SBC, but no ADD nor SUB. Many variations of the PIC architecture have ADD and SUB, but no ADC nor SBC. Two-byte addition would generally use a conditionally-skipped increment for the upper byte. Longer addition uses a trickier sequence of operations which ends up needing (4n-2) instructions total to add an n-byte number to another n-byte number when the destination is the same as one of the sources.
    – supercat
    Commented Apr 9 at 20:51
  • 3
    In other words, this is an addition of a 32-bit value stored in DX (most significant word) and AX (least significant) with 0xFFFFFFFC (i.e. subtracting 4 from that 32-bit number).
    – jcaron
    Commented Apr 10 at 11:34
  • Further, on the 6502, SBC is implemented simply by inverting all the bits of the operand and then doing an ADC. This is why you need to set the carry before doing SBC so the combination of inverting the bits and adding one (the carry) is equivalent of taking the 2's complement.
    – JeremyP
    Commented Apr 10 at 15:01
1
add     ax, 0FFFCh
adc     dx, 0FFFFh ; <-- here

add ax,0FFFCh --> Adds ax and FFFC, if ax is at least 4 to start with it will cause an overflow and set the carry flag.

adc dx, 0FFFFh --> Adds dx and FFFF and 1 if carry flag is set. This has the effect of subtracting 1 from dx unless the flag was set by the previous operation. It will also set the carry flag if dx is at least 1 or if the carry flag is set, but as you say it isn't used after this add.

The effect is something like this in pseudocode:

ax = ax + 0xFFFC;
carry_flag = ax > 0xFFFF ? 1 : 0;
ax = ax & 0xFFFF;
dx = carry_flag ? dx : dx - 1;

Adding 0xffff to a 16 bit register has the effect of subtracting 1 but also sets the carry flag if the value is non-zero. If the carry flag is set when the operation is executed then an additional 1 is added and the carry flag will always be set then.

1
  • 1
    The original compiled c code was probably something like this : long int somevar; SomeFunction(..., somevar-4, ...)
    – tigrou
    Commented Apr 11 at 14:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .