Question

I'm trying to understand how this line works:

lea (%eax, %eax, 4), %eax

So it looks like this essentially says:

%eax = 5* %eax

But does LEA treat as signed or unsigned?

Was it helpful?

Solution

  1. LEA is not really intended to do arithmetic operations but to calculate the absolute address of a memory operation.

  2. There would be no difference between a signed and an unsigned operation; the result for a signed operation and an unsigned operation is the same in this case.

  3. As far as I know there is no possibility to multiply a register with a negative constant using the LEA instruction

OTHER TIPS

LEA does binary math, which is the same operation for unsigned or 2's complement signed. Just like the add and shl instructions. (Except lea doesn't set FLAGS, so you can't tell whether there was signed overflow (OF) or unsigned wrap-around (CF)).

For more about the general case of LEA as a shift-and-add instruction, see Using LEA on values that aren't addresses / pointers? - It's x86's only way to do a copy-and-add that writes the result into a different register, preserving the original, so compilers don't think about it as being "for addresses", and neither should you.


The only definite signedness is the way constants are encoded into machine code. A one-byte disp8 or 4-byte disp32 gets sign-extended when decoded. e.g.

lea  -4(%rdi), %eax        # int foo(int x){ return x-4; }

is the same instruction (with the same machine code: 8d 47 fc lea eax,[rdi-0x4]) as

lea  0xfffffffffffffffc(%rdi), %eax

Or to put it another way, assemblers can compress small-magnitude constants into 1 byte when (int8_t)c == c.


In 64-bit mode with 64-bit address-size, a disp32 or RIP+rel32 in machine code also gets sign-extended to 64-bit. (The only 64-bit absolute memory addressing is via a special encoding of mov; RIP+rel32 is far enough to reach anything in an executable or library with up to 2GiB of code+data, so it would be a waste of code footprint to use 8 byte displacements all the time.)


If you override the address-size to 32 and override the operand-size to 64, you effectively get zero-extension (like for unsigned), because 32-bit address works by truncation, not sign-extension. So addresses are contiguous from 0..4G, not the low and high 2GiB of 64-bit address space.

(Therefore it's silly to do that, just use 64-bit address size and 32-bit operand size so you don't need any prefixes; implicit zero-extension to 64-bit via writing a 32-bit register is exactly equivalent.)

# objdump -d output  for two equivalent instructions
   67 48 8d 47 fc          lea    -0x4(%edi),%rax    # address-size + REX.W + opcode + modrm + disp8
   8d 47 fc                lea    -0x4(%rdi),%eax    # no prefixes, same rest of the insn

I used 32-bit in 64-bit mode because other cases of a narrow result just merge, leaving the high bytes of the destination unchanged, so there's no zero or sign-extension. I guess I could have used 16-bit address-size in 32-bit mode, with 32-bit operand-size, like

lea  -1(%si), %eax    # zero-extend (uint16_t)(SI-1) into EAX

But lea -1(%esi), %ax is not equivalent to that, it only writes AX.

This would be more appropriate as a comment, but comments don't have code formatting. As was hinted at by other comments and Martin's answer, you could do something like the following (gas syntax):

// %ecx has a size in it
neg %ecx
lea (%esp, %ecx, 4), %esp /* subtract %ecx*4 from stack ptr */
neg %ecx

... as compared with something like:

lea (,%ecx,4), %edx
sub %edx, %esp

... or:

lea (,%ecx,4), %edx
neg %edx
lea (%esp,%edx), %esp

I can't comment on the performance differences, but sub will alter flags and lea will not.

It is treated as signed. Anything done using the bracket [] is signed. 2's complement is a way to represent signed numbers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top