如何从内存中读取数组中的值,然后在 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,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,4                #incrementing i
            addi $t1,$t1,4                #incrementing $t1
            j for

目前正在打印的是 array[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 (word) 的指针。因此,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,$t6   # add to base of array
lw $t1,($t6)       # access element of the array
,

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

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

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

如果你有 .word 元素,另一个选择是运行两个计数器:一个带有 add r,r,4 的指针和一个用 add 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,$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,1
        bne   $t0,$t5,loop    # }while(++p != endp);

      li   $v0,10
      syscall            # exit()

请注意,我将值加载到 $a0 中,否则不要浪费移动指令。 $a0$t1 一样是一个普通的寄存器;它不是“保留”供系统调用使用的,当 syscall 要求“操作系统”(或在本例中为模拟器)对那里的值执行某些操作时,您只需要那里的值。

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

  main:
# only use $t registers; we don't need them to survive across a function call
      la   $t4,$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,$t4            # i = p - arr
               # for print_int
               ...
            lb    $a0,0($t0)              # sign_extend_int8_to_word (*p)
               # for print_int
               ...
            addiu $t0,1
            bne   $t0,10
      syscall            # exit()

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

剩下的就是打印。

(除了 bge 之外的两个寄存器之间的$zero 是 slt/bne 的伪指令;这就是为什么在 MIPS 中,计算循环结束条件或将某些值递减到零通常是个好主意因此您可以使用 bnebeq 作为循环条件。您的循环可以使用 beq,因为将精确达到计数。)

另请注意,在我的 .data 部分中,我避免使用 .word 5,因为我在数组的末尾放置了一个标签,因此我可以使用 la 来获取端点。或者我可以使用 .eqv array_size,5,以便我以后可以使用 li $t5,array_size。 (注意 li 不是 lwla。它将组装为 li $t5,5,或者更具体地说是 addiu $t5,$zero,5

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