如何使用静态数组的结束指针作为循环条件来比较 x86 中的地址?

How to compare addresses in x86, using the end pointer of a static array as a loop condition?

从头开始编程的挑战之一是“修改程序以使用结束地址而不是数字 0 来知道何时停止。”。 =15=]

我发现很难做到这一点,因为到目前为止,本书只介绍了 movlcmplincl(以及寻址模式)和 jmp 说明。基本上下面的代码片段中的所有内容都是到目前为止所介绍的内容。我找到的所有解决方案都涉及本书尚未介绍的说明。下面的代码从集合中找到最大值。

.section .data
data_items:             #These are the data items
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

.section .text
.globl _start
_start:
    movl [=10=], %edi                   # move 0 into the index register
    movl data_items(,%edi,4), %eax  # load the first byte of data
    movl %eax, %ebx                 # since this is the first item, %eax is
                                    # the biggest
start_loop:                     # start loop
    cmpl [=10=], %eax                   # check to see if we’ve hit the end
    je loop_exit
    incl %edi                       # load next value
    movl data_items(,%edi,4), %eax
    cmpl %ebx, %eax                 # compare values
    jle start_loop                  # jump to loop beginning if the new
                                    # one isn’t bigger
    movl %eax, %ebx                 # move the value as the largest
    jmp start_loop                  # jump to loop beginning
loop_exit:
    # %ebx is the status code for the exit system call
    # and it already has the maximum number
    movl , %eax   #1 is the exit() syscall
    int [=10=]x80

注意这个问题与随后的问题明显不同,后者要求修改程序以使用长度计数而不是数字 0。对我来说,数组中最后一个数字的地址似乎应该存储在一个寄存器,然后与指针的地址进行比较。我想不出适合本书进展的方法,因为到目前为止本书只介绍了基本内容。

您只需 movcmp 即可完成此操作,不需要 lea 来计算终点指针。 (无论如何,您在任何地方都没有长度可用于 LEA)。

你应该在数组的末尾添加一个新标签,这样你就可以在内存中引用那个位置(也就是地址)。 并删除终止符 0 来自数组,因为我们使用的是地址而不是标记值。

.section .data
data_items:
  .long 3,67,34,222,45,75,54,34,44,33,22,11,66     # ,0   remove the sentinel / terminator
data_items_end:                                  # and add this new label

您不需要在寄存器中使用该地址;您可以使用 cmp $data_items_end, %reg 将其用作立即数,链接器将正确的字节填充到机器代码中,就像它对您的 mov data_items(,%edi,4), %eax 所做的那样。 (cmp symbol, %reg 将与该地址处的内存进行比较。$symbol 是 AT&T 语法中的立即地址。)

在寄存器中需要的是start地址,所以你可以递增和取消引用它。 (对于一个函数采用指针+长度,您可以计算寄存器中的结束地址。)

_start:
    mov  $data_items, %edi       # int *ptr = &data_items[0]
    mov  (%edi), %ebx            # current max
   # setting %eax is unnecessary here, it's always written before being read in this and the original version
loop_start:
    add  , %edi                # ptr++  (4 byte elements)
    cmp  $data_items_end, %edi
    je   loop_exit               # if (ptr == endp) break
    ...                  # compare with (%edi) and update %ebx if greater.
    jmp  loop_start
  ...

效率更高的是 ,特别是因为您知道数组包含超过 1 个元素,所以您不需要检查循环体应该 运行 0 次的情况.请注意,除了 cmp/jcc.

之外,没有每次都必须执行的无条件 jmp
_start:
    mov  $data_items, %edi       # int *ptr = &data_items[0]
    mov  (%edi), %ebx            # current max

loop_start:                    # do{
    add  , %edi                # ptr++;  (4 byte elements)
  ## maybe update max:
    mov  (%edi), %eax            # tmp = *ptr;
    cmp  %ebx, %eax
    cmovg %eax, %ebx             # max = (tmp > max) ? tmp : max;
  ## end of loop body

    cmp  $data_items_end, %edi
    jne  loop_start            # }while(ptr != endp)
## end of loop, but nothing jumps here so no label is needed.

    mov  , %eax
    int  [=12=]x80             # SYS_exit(%ebx)

我使用 cmp/cmovg(条件移动)而不是分支只是因为它的输入指令更少并且在 循环中没有分支 ,使得更容易看到循环结构。


循环和指针的其他示例:

  • - 将指针+长度作为参数并使用 LEA 计算结束指针的函数。 (x86-64 NASM 语法)
  • How to check an "array's length" in Assembly Language (ASM), - 根据 .long 静态数组的长度定义一个 assemble 时间常数,而不是在末尾放置标签。
  • Copying to arrays in NASM - 编写在 two 数组上循环的高效循环的一些技巧,例如相对于另一个索引一个仍然只使用一个增量但避免索引寻址模式。或者将负索引向上计数为零,因此您仍然可以在内存中向前循环但仍然不需要单独的 cmp 指令,只需 inc / jnz.