-1
mov ax, 0ffffh
inc ax 
inc ax

Was watching a video on basic Intel X86 Assembly.

I thought 0FFFFH was 65535 but in the video they got -1 instead (before the inc instructions run). Just wondering how and why?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
localhost
  • 25
  • 3
  • 1
    And how would **you** expect `-1` to look like? – PM 77-1 May 27 '22 at 15:13
  • 4
    Are you generally familiar with [two's complement arithmetic](https://en.wikipedia.org/wiki/Two%27s_complement)? 65535 and -1 are the same number, mod 65536, and so they have the same representation in two's complement arithmetic. – Nate Eldredge May 27 '22 at 15:20

1 Answers1

3

0xFFFF, 65535, and -1 are all the same bit pattern: sixteen one's, no zeros — so it is a matter of interpretation as to which we choose to print or say.

It is an important aspect of programming to consistently view your values the same way, and in assembly/machine code it is very easy to mix up unlike things.

In high level languages we have logical variables, and give types to variables, so whenever the variable is used, the program knows whether the variable is signed or unsigned or other.  Debuggers for high-level languages also know the type of declared variables, so will never print -1 for an unsigned integer, for example.

However, in assembly we have physical storage, and this storage is untyped — just some bits being stored.  So, we can interpret those bits in a variety of ways, as signed numbers, as unsigned numbers, as characters (e.g. unicode or other character set), etc..

Machine code debuggers & instruction set simulators generally don't know what type the programmer intended, as assembly language is lacking in this type information, plus what little is available is lost in machine code.  It is up to the program to consistently treat storage as to what type it is, and so then up to the programmer to view physical storage with the proper type in mind.

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53
  • Or if you fully grok 2's complement, it's more convenient to write `-1` for all-one bits than writing the exact right number of `f`s. For example `_mm_xor_si128(v, _mm_set_epi32(-1))` for a SIMD `not`. Or `mov eax, -1` to set all bits of an integer register; I'm thinking about it as bits, and that's a compact way to write it which takes no mental effort to transform either way. All-ones = -1 is a really important fact sometimes, e.g. for SSE2 `pcmpeqb xmm0, xmm1` / `psubd xmm2, xmm0` to count matches. – Peter Cordes May 27 '22 at 15:56
  • There's also the trick: `and rsp, -32` to align the stack by 32 (clear the low 5 bits, leave the others unmodified) instead of writing out `and rsp, 0xffffffffffffffe0` or `and rsp, 0xffffffffffffffff << 5`. But for me that's more of a trick to remember separately, not re-invent on the fly. I can derive from 2's complement identities why it works, but I don't do that every time I write it. – Peter Cordes May 27 '22 at 15:58
  • Maybe a better example of understanding two's complement vs. unsigned is optimizing `x >= 0x8000` unsigned into `test ax,ax` / `js`. Or range-check stuff like `sub ax, low` / `cmp ax, high-low` / `ja outside_range`, – Peter Cordes May 27 '22 at 16:04