如何从内存中的数组中读取一个值,然后递增 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 条循环内的实际工作指令才能在正确的时间将 i
和 arr[i]
放在我们想要的位置。 (bne
作为循环分支而不是 j
意味着我们避免在顶部使用 bge
。而且4条指令都是简单高效的,不包括div
.
剩下的只是打印。
( $zero
以外的两个寄存器之间的 bge
是 slt/bne 的伪指令;这就是为什么在 MIPS 中计算循环结束条件或计数通常是个好主意一些接近零的东西,所以你可以使用 bne
或 beq
作为循环条件。你的循环可以使用 beq
因为计数将完全达到。)
另请注意,在我的 .data
部分中,我避免了 .word 5
,因为我在数组的末尾放置了一个标签,因此我可以使用 la
来结束-指针。或者我可以使用 .eqv array_size, 5
,这样我以后就可以使用 li $t5, array_size
。 (注意 li
不是 lw
或 la
。它将 assemble 到 li $t5, 5
,或者更具体地说 addiu $t5, $zero, 5
。
将一个小整数常量放入数据存储器并使用 lw
从那里加载它是低效的,所以我避免了它。
所以我正在编写一段代码,用于在 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 条循环内的实际工作指令才能在正确的时间将 i
和 arr[i]
放在我们想要的位置。 (bne
作为循环分支而不是 j
意味着我们避免在顶部使用 bge
。而且4条指令都是简单高效的,不包括div
.
剩下的只是打印。
( $zero
以外的两个寄存器之间的 bge
是 slt/bne 的伪指令;这就是为什么在 MIPS 中计算循环结束条件或计数通常是个好主意一些接近零的东西,所以你可以使用 bne
或 beq
作为循环条件。你的循环可以使用 beq
因为计数将完全达到。)
另请注意,在我的 .data
部分中,我避免了 .word 5
,因为我在数组的末尾放置了一个标签,因此我可以使用 la
来结束-指针。或者我可以使用 .eqv array_size, 5
,这样我以后就可以使用 li $t5, array_size
。 (注意 li
不是 lw
或 la
。它将 assemble 到 li $t5, 5
,或者更具体地说 addiu $t5, $zero, 5
。
将一个小整数常量放入数据存储器并使用 lw
从那里加载它是低效的,所以我避免了它。