如何从内存中的数组中读取一个值,然后递增 MIPS 中的计数器?

How do I read a value from an array from memory and then increment the counter in MIPS?

所以我正在编写一段代码,用于在 for 循环中打印出数组的元素。 此代码的 C 版本如下所示:

   for(i=0; i<arraySize; i++)
    {
      printf("Array[%i]=%i\n", i, array[i]);
    }

我在MIPS中比较的代码是这样的:

.data
    array:      .byte 2,3,5,7,11
    array_size:     .word 20
    array_print1:   .asciiz "Array["
    array_print2:   .asciiz "]="
    newline:    .asciiz "\n"
    sum_print:  .asciiz "Sum of the array is "
.text
    main:
        lw $s1, array_size                  #int arraySize = 5
        la $s0, array                       #int array[] = {2, 3, 5, 7, 11}
        
        add $t0, $zero, $zero                   #int i = 0
        
        j for                           #go to for                      
        for:
            bge $t0, $s1, outsidefor            #i < arraySize otherwise leave for
            
            li $v0, 4                   #printing 'Array['
            la $a0, array_print1
            syscall
            
            div $t2, $t0, 4
            mflo $t2
            li $v0, 1                   #printing index
            move $a0, $t2
            syscall
            
            la $v0, 4                   #printing ']='
            la $a0, array_print2
            syscall
            
            lw $t1, 0($s0)                  #getting array[i]
            
            li $v0, 1                   #printing array[i]
            move $a0, $t1
            syscall
            
            li $v0, 4
            la $a0, newline                 #printing new line
            syscall
            
            addi $t0, $t0, 4                #incrementing i
            addi $t1, $t1, 4                #incrementing $t1
            j for

当前正在打印的是数组[i] 中值的地址(我相信是这样)。我在这里做错了什么?

所以事实证明我必须通过 addi $s0, $s0, 4 将 $s0 增加 4 而且我还必须将 lw $t1... 切换为 lb 或将 .data 中的字节向上切换为 .word

您的 C 代码使用数组引用,但您已将汇编代码转换为使用指针。我建议,如果您想进行此转换,请先在 C 中进行,然后您的汇编代码中就会有匹配的 C 代码。

(在转换为汇编的过程中进行算法转换很容易出错,从数组引用转换为指针就是这样;另一个是将 while 循环更改为 do while)

与您正在做的事情等效的 C 代码(也许现在有了您的更新):

int array[] = ...
int arraySize = (sizeof array) * 4;

int main ()
{
    int *pi = array;
    for(i=0; i < arraySize; i += 4)
    {
        printf("Array[%i]=%i\n", i/4, *pi);
        pi++;  // in C, pointer increments automatically scale
    }
}

C 知道 pi 的类型是指向 int(字)的指针。因此,C 会根据指向的大小自动缩放常量(这里是 pi += 1 中的 1,pi++ 的缩写)。

您可以看到 C 代码不需要将 i 递增 4,也不需要将其除以 4 — 这应该只递增 1 而没有除法。

C 编译器知道数据类型并记住它们。在汇编中,理解类型主要是程序员的工作,我们需要理解并在以下情况下使用相同的匹配大小:声明数据、取消引用和指针运算——如果其中任何一个不一致,事情就不会起作用。


MIPS 中整数数组的数组引用如下:

la $s0, array       # this one can be moved out of the loop, but is ok inside the loop
sll $t6, $t0, 2     # scale i by 4
add $t6, $s0, $t6   # add to base of array
lw $t1, ($t6)       # access element of the array

根据您的回答(addi $s0, $s0, 4 递增指针),您现在在数组上有一个正常的指针递增循环。但出于某种原因,您已经将另外两个循环计数器递增 4。您可以 addu $t3, $t0, $s0 将该字节偏移量添加到数组基址并获得 &array[i],准备与 lw 一起使用.

使用 div 获取数组索引难以置信 低效。简单地将另一个寄存器用于另一个递增 1 而不是 4 的循环计数器会更有效。

另外,如果你的数组元素都很小,那么数组下标就是一个字节的偏移量,或者你可以只用一条subu指令来转换一个指针到一个元素回到一个索引。 (在 C 中,i == &arr[i] - arr。)

如果你有 .word 个元素,另一种选择是 运行 两个计数器:一个带有 add r,r,4 的指针和一个用 add r,r,1 打印的 int 索引.

.data
    array:      .byte 2,3,5,7,11
    array_end:
 .eqv   array_size,  5       # assemble-time constant.  But can't get MARS to calculate it from the actual array size, and hard-coding sucks.
    array_print1:   .asciiz "Array["
    array_print2:   .asciiz "]="
  # newline with print_char not print_string
    sum_print:  .asciiz "Sum of the array is "

.text
  main:
# only use $t registers; we don't need them to survive across a function call
      la   $t4, array                      # int8_t *arr = array;
      la   $t5, array_end                  # endp
      move $t0, $t4                        # int8_t *p = array

# we assume size is non-zero so we only need to check the count at the bottom
   loop:                           # do{
        li $v0, 4                    #printing 'Array['
        la $a0, array_print1
        syscall
        
        li   $v0, 1                   #printing index
        subu $a0, $t0, $t4            # i = p - arr
         # srl  $a0, $a0, 2         # if you had word elements, C pointer subtraction would involve scaling by the element size
        syscall
        
        li $v0, 4                   #printing ']='.  # This was la which works but isn't idiomatic
        la $a0, array_print2
        syscall
        
        li $v0, 1                   #printing array[i]
        lb $a0, 0($t0)              # sign_extend_int8_to_word (*p)
        syscall
        
        li  $v0, 11
        li  $a0, '\n'
        syscall                     # print_char(newline)
        
        addiu $t0, $t0, 1
        bne   $t0, $t5, loop    # }while(++p != endp);

      li   $v0, 10
      syscall            # exit()

请注意,我将 $a0 加载到我想要值的位置,否则不要浪费移动指令。 $a0$t1 一样是一个普通寄存器;它不是“保留”供系统调用使用,当 syscall 询问“OS”(或在本例中为模拟器)用那里的值做某事时,你只需要在那里的值。

有了所有打印字符串的代码,程序看起来并没有小很多,但如果你看看之后剩下的,我的版本是简单得多

  main:
# only use $t registers; we don't need them to survive across a function call
      la   $t4, array                      # int8_t *arr = array;
      la   $t5, array_end                  # endp
      move $t0, $t4                        # int8_t *p = array

# we assume size is non-zero so we only need to check the count at the bottom
   loop:                           # do{
            subu  $a0, $t0, $t4            # i = p - arr
               # for print_int
               ...
            lb    $a0, 0($t0)              # sign_extend_int8_to_word (*p)
               # for print_int
               ...
            addiu $t0, $t0, 1
            bne   $t0, $t5, loop    # }while(++p != endp);

      li   $v0, 10
      syscall            # exit()

只有 4 条循环内的实际工作指令才能在正确的时间将 iarr[i] 放在我们想要的位置。 (bne 作为循环分支而不是 j 意味着我们避免在顶部使用 bge。而且4条指令都是简单高效的,不包括div.

剩下的只是打印。

( $zero 以外的两个寄存器之间的 bge 是 slt/bne 的伪指令;这就是为什么在 MIPS 中计算循环结束条件或计数通常是个好主意一些接近零的东西,所以你可以使用 bnebeq 作为循环条件。你的循环可以使用 beq 因为计数将完全达到。)

另请注意,在我的 .data 部分中,我避免了 .word 5,因为我在数组的末尾放置了一个标签,因此我可以使用 la 来结束-指针。或者我可以使用 .eqv array_size, 5,这样我以后就可以使用 li $t5, array_size。 (注意 li 不是 lwla。它将 assemble 到 li $t5, 5,或者更具体地说 addiu $t5, $zero, 5

将一个小整数常量放入数据存储器并使用 lw 从那里加载它是低效的,所以我避免了它。