Assembly bit memory limit in arithmetic

2019-03-01 04:30发布

问题:

I wanted to add the following numbers: 40, 90, 50 and 155 and I get a total of 355.

I wanted to experiment and test out whether the register AL will have a bit limit of (2^8) - 1, and when I compiled the code and execute the code, I get decimal of 1376331855. How did that happen?

Also, I thought 355 is greater than 255, and as a result should display an overflow exception.

I understand if I use MOVZX I will be able to carry the calculation into the higher register of AX.

Also, I am very confused with the difference between AL and AH. Is there a different in memory allocation for AL and AH?

TITLE Adding              
INCLUDE Irvine32.inc

.code
main PROC

    mov al,0h             ; 
    add al,28h            ; 40

    add al,5Ah            ; 90 
    add al,32h            ;50
    add al,9Bh            ;155
                            ; total is 355
    call DumpRegs
    call writedec

exit
main ENDP
END main

回答1:

As I understand it, DumpRegs gives you the output of EAX. When I convert your answer to HEX, I get 5209284F, the 4F being in the AL. 4F HEX is 79 Decimal, which is 335 - 256. The AL register only holds 8 bits, so 256 is the maximum unsigned integer it can hold.

Clear EAX before you begin and the results may make more sense.



回答2:

Also, I am very confused with the difference between AL and AH. Is there a different in memory allocation for AL and AH?

No, there's no memory involved. They're both byte registers within EAX.

  • AX is the low 16 bits of EAX
  • AH and AL are the high and low halves of AX

See also this ascii-art diagram. Or in C:

union eax {
    uint32_t EAX;                // regs aren't really signed or unsigned, but they have well-defined wraparound semantics like C unsigned (and unlike C signed).
    uint16_t AX;
    struct { uint8_t AL, AH; };  // anonymous struct, in little-endian order (AL is the low byte).
};

Writes to any member are reflected in the values of other members, but don't zero the rest of the register. (footnote1)


Your print function prints all of EAX, but you never zeroed the high bytes of EAX before printing it. On entry to main, you need to assume that all bytes of EAX are random garbage.

main PROC

    xor    eax, eax       ; zero eax
    ; mov al,0h      ; instead of just zeroing al and leaving garbage in the upper 24 bits
    add    al,28h         ; then play around with the low byte if you want
    ...
    add    al,9Bh         ; AL wraps around, but no carry happens into the rest of EAX.
    ;  If you want that, use a wider register:
    ; add   eax, 9Bh

    call writedec         ; prints eax as a signed integer

I thought 355 is greater than 255, and as a result should display an overflow exception.

Integer overflow sets flags, which you can test later. See Understanding Carry vs. Overflow conditions/flags

It doesn't trigger faults / exceptions. (Except for division)


(1): Strict ISO C90, and ISO C++, actually don't allow reading a union member that wasn't the last one written (undefined behaviour). ISO C99, (and GNU C++ as an extension) do guarantee type-punning with unions works as expected.