从头开始编程的挑战之一是“修改程序以使用结束地址而不是数字 0 来知道何时停止。”
我发现很难做到这一点,因为到目前为止,这本书只介绍了 movl
、cmpl
、incl
(以及寻址模式)和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 $0, %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 $0, %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 $1, %eax #1 is the exit() syscall
int $0x80
请注意,这个问题与随后的问题明显不同,后者要求修改程序以使用长度计数而不是数字 0。对我来说,数组中最后一个数字的地址似乎应该存储在寄存器中然后与指针的地址进行比较。我无法找到一种适合本书进展的方法,因为到目前为止,这本书只介绍了大概的框架。
最佳答案
您只需使用 mov
即可做到这一点和cmp
,否 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:
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 $4, %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
...
更高效的是 do{}while
loop structure like compilers use ,特别是因为您知道数组包含超过 1 个元素,因此您无需检查循环体应运行 0 次的情况。请注意,没有无条件 jmp
除了 cmp/jcc 之外,它每次都必须执行。
_start:
mov $data_items, %edi # int *ptr = &data_items[0]
mov (%edi), %ebx # current max
loop_start: # do{
add $4, %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 $1, %eax
int $0x80 # SYS_exit(%ebx)
我用过cmp
/cmovg
(条件移动)而不是仅仅因为输入的指令较少而进行分支,并且循环内没有分支,从而更容易查看循环结构。
循环和指针的其他示例:
- Assembly Language (x86): How to create a loop to calculate Fibonacci sequence - 采用指针+长度作为参数,并使用 LEA 计算结束指针的函数。 (x86-64 NASM 语法)
- How to check an "array's length" in Assembly Language (ASM), - 根据
.long
的长度定义汇编时间常数静态数组,而不是在末尾放置标签。 - Copying to arrays in NASM - 编写在两个数组上循环的高效循环的一些技巧,例如相对于另一个进行索引仍然只使用一个增量,但避免索引寻址模式。或者将负索引向零计数,这样您仍然可以在内存中向前循环,但仍然不需要单独的 cmp 指令,只需
inc / jnz
.
关于linux - 如何使用静态数组的结束指针作为循环条件来比较 x86 中的地址?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68463932/