将值存储到数组的每个元素中:将 C do{}while 循环转换为 MIPS asm
Store a value into every element of an array: converting a C do{}while loop to MIPS asm
我需要将此 C++ 函数转换为 MIPS 程序集:
int set(int a[], int n, int v)
{
int i;
i = 0;
do {
a[i++] = v;
} while ( i < n);
return i;
}
其中数组的地址在$a0
,n在$a1
,v在$a2
。
这是我在 MIPS 中编写函数的尝试,但我得到“指令引用 0x00400054 处的未定义符号”。在 main 中(由我的教授提供)有一个调用 jal set
应该调用 set
函数,我很确定我的错误与此有关。我也不知道我是否成功转换了功能。这是我的尝试:
.text
set:
li $t0, 0
addi $t0, $t0, 0
sll $t1, $t0, 2
add $t1, $t1, $a0
L1: lw $t1, 4($t1)
sw $a2, 0($t1)
blt $t0, $a1, L1
我正在使用 QTSPIM,如果这很重要的话。我很感激任何帮助,如果你有任何关于 MIPS 编程的建议,那也很好。
更新:
现在正在链接文件,但我遇到了“异常发生在 PC=0x004000f0”和“data/stack 中的错误地址读取:0x00000000”的无限循环。这是我的更新文件:
.text
.globl set
set: addi $t0, $t0, 0 #i = 0;
sll $t1, $t0, 2 #offsets 4 * i
add $t1, $t1, $a0 #pointer to a[i]
L1: lw $t1, 4($t1) #a[i++]
sw $a2, 0($t1) #a[i++] = v
blt $t0, $a1, L1 #continue the loop as long as i < n
为什么我的代码必须在 .globl
中? .text
的目的是什么?
好的,有一些问题。
更正后的代码在底部。实际上有两种方法可以做到这一点,具体取决于 C 代码的翻译需要达到的字面意思。您的部分概念问题可能是您试图将这两种方法的部分结合起来。
根据评论反馈[重复](例如 li
然后 addi
),您将原始代码中的前两条指令合并为一条。但是,如果只使用一个,li
是正确的,因为 addi
将寄存器添加到自身,但你不能依赖初始值为零。
sll
正在移位一个值为零的寄存器,因此 inst 没有做任何事情。
要用 a0
加载 t1
,您需要使用 add $t1,$a0,0
[或 add $t1,$a0,$zero
]
lw
没有用 [C 代码不加载 a
,为什么要 asm?]。
但是,我稍微改变了一下,因为循环仍然无法正常工作。
在你的 blt
之后没有 return 所以即使循环有效,它也会 "fall off the edge of the world"。每个 called asm 例程 [像 jal set
一样被调用的例程] 必须有一个 explicit return 语句,它是 jr $ra
注意:在 MIPS asm 中,a*
[参数寄存器]可以被被调用者修改,所以循环在 a0
而不是 t1
(即调用者 期望 他们将被丢弃)
无论如何,这是更正后的代码[请原谅不必要的样式清理]:
.text
.globl set
set:
li $t0,0 # i = 0
L1:
sw $a2,0($a0) # a[i] = v
add $a0,$a0,4 # advance pointer
add $t0,$t0,1 # ++i
blt $t0,$a1,L1 # continue the loop as long as i < n
jr $ra # return
如果您的原始 C 函数类似于:
int
set(int *a, int n, int v)
{
int *end;
end = a + n;
for (; a < end; ++a)
*a = v;
return n;
}
那么,这将是一个更直白的翻译:
.text
.globl set
set:
sll $a1,$a1,2 # convert int count to byte length
add $a1,$a0,$a1 # end = a + length
L1:
sw $a2,0($a0) # *a = v
add $a0,$a0,4 # ++a
blt $a0,$a1,L1 # continue the loop as long as a < end
jr $ra # return
IMO,这两种方法都是原始 C 函数的可接受实现。第一个更直白,因为它保留了索引变量 i
的 [概念]。但是,它有一条第二条没有的额外指令。
优化器可能会生成相同的代码(即第二个 asm),而不管它正在翻译哪个 C 函数(MIPS 没有 x86
asm 所具有的强大索引寻址模式)。
所以,"correct" 可能取决于你的教授有多坚持。
旁注:请注意我的两个示例之间的样式变化。也就是说,代码更改放在一边,添加一些空行以提高清晰度。
为了完整起见,这里是我在测试时创建的 main
函数:
.data
arr: .space 400 # allocate more space than count
.text
.globl main
main:
la $a0,arr # get array pointer
li $a1,10 # get count
li $a2,3257 # value to store
jal set
li $v0,1 # exit syscall number
syscall
更新:
If in the loop a[i++] = v
, would I place the add $t0, $t0, 1
line before the sw $a2, 0($a0)
?
不,如果 C 代码是 a[++i] = v
,您会这样做。也许,最好的查看方式是先简化 C 代码。
a[i++] = v
实际上是:
a[i] = v;
i += 1;
而且,a[++i] = v
实际上是:
i += 1;
a[i] = v;
现在,C代码行和asm指令一一对应了。
When would I use the sll
? I was reading examples and I noticed people usually do sll $t1, $t0, 2
when they're going to use a counter to go through an array.
是的。如果您仔细查看我的 second 实现,它会以这种方式使用 sll
。此外,即使给出原始 C 代码,这也是我编写循环代码的方式。
Would I use the lw
if the C code said something like int x = a[0]
?
是的,完全正确。
另一种制作 asm 原型的方法是将 C 代码转换为 "very dumb C".
也就是说,只有 if
最简单的形式:if (x >= y) goto label
。甚至 if (x < y) j = 10
也是禁区。
没有函数作用域变量或函数参数变量——只有作为寄存器名称的全局变量。
没有复杂的表达。只有简单的 x = y
、x += y
或 x = y + z
。因此,a = b + c + d
太复杂了。
注册变量同时用作整数值和字节指针。因此,当添加到用作指针的寄存器时,就像添加到字节指针一样,因此要递增 int
数组,您必须添加 4
.
字节指针和 int
指针之间的实际区别仅在您执行 load/store 操作时才重要:lw/sw
用于 int
和 lb/sb
用于字节。
所以,这是我的第二个 C 函数,重新编码为 "dumb":
// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{
v0 = a1; // set return value
a1 <<= 2; // convert int count to byte length
a1 += a0; // point to one past end of array
L1:
*(int *)a0 = a2; // store v at current array location
a0 += 4; // point to next array element
if (a0 < a1) goto L1; // loop back until done
return;
}
更新#2:
In your first implementation, is add $a0, $a0, 4
the equivalent to using sll
in the second implementation?
不完全是。要记住的关键是在 C 中,当我们将 1 添加到指针 [或使用 i
对其进行索引时,编译器将生成一条 increment/add 指令,该指令将 sizeof
添加到 type 指针定义为。
也就是说,对于int *iptr
,指定iptr += 1
将生成add $a0,$a0,4
,因为sizeof(int)
是4。如果我们有double *dptr
,dptr += 1
, 编译器会生成 add $a0,$a0,8
因为 sizeof(double)
是 8
这是 C 编译器提供的一个强大的"convenience",因为它允许数组、指针、索引可以互换使用。
在 asm 中,我们必须手动完成 C 编译器会自动为我们完成的工作。
考虑以下情况:我们有一个值,它是数组中 个元素 的计数,称它为 count
。现在,我们想知道数组将占用多少字节。我们称之为 len
。这是用于确定各种类型的 C 代码:
char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
// sll $a1,$a1,0
short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
// sll $a1,$a1,1
int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
// sll $a1,$a1,2
double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
// sll $a1,$a1,3
From what I understand, using sll sets i as a counter for ints so it increments i and also iterates the array
没有。 sll
只是 MIPS 的 "shift left logical" 指令,当您需要 C 的 <<
运算符时可以使用它。
你在想的是sll
如何使用来达到那个效果。
为了遍历 int
的数组,我们将 index 递增 1,但我们还必须将数组指针递增 4。这就是我的第一个 asm例子做到了。终止条件为index >= count
.
在我的第二个 asm 示例中,我通过将元素计数转换为字节长度(通过 ssl
)并添加数组地址来消除单独的索引变量。现在 $a1
得到了数组最后一个元素的地址 + 1,终止条件是 current_address >= last_element_plus_1
。请注意,current_address ($a0
) 仍然必须递增 4 (add $a0,$a0,4
)
要记住的一件重要事情是 asm 指令 [特别是 MIPS] 很简单(即 dumb)。他们一次只做一件事。如果语句足够复杂,单个 C 赋值语句可以生成大约 20 条指令。 asm 指令的组合方式产生了更复杂的结果。
我需要将此 C++ 函数转换为 MIPS 程序集:
int set(int a[], int n, int v)
{
int i;
i = 0;
do {
a[i++] = v;
} while ( i < n);
return i;
}
其中数组的地址在$a0
,n在$a1
,v在$a2
。
这是我在 MIPS 中编写函数的尝试,但我得到“指令引用 0x00400054 处的未定义符号”。在 main 中(由我的教授提供)有一个调用 jal set
应该调用 set
函数,我很确定我的错误与此有关。我也不知道我是否成功转换了功能。这是我的尝试:
.text
set:
li $t0, 0
addi $t0, $t0, 0
sll $t1, $t0, 2
add $t1, $t1, $a0
L1: lw $t1, 4($t1)
sw $a2, 0($t1)
blt $t0, $a1, L1
我正在使用 QTSPIM,如果这很重要的话。我很感激任何帮助,如果你有任何关于 MIPS 编程的建议,那也很好。
更新:
现在正在链接文件,但我遇到了“异常发生在 PC=0x004000f0”和“data/stack 中的错误地址读取:0x00000000”的无限循环。这是我的更新文件:
.text
.globl set
set: addi $t0, $t0, 0 #i = 0;
sll $t1, $t0, 2 #offsets 4 * i
add $t1, $t1, $a0 #pointer to a[i]
L1: lw $t1, 4($t1) #a[i++]
sw $a2, 0($t1) #a[i++] = v
blt $t0, $a1, L1 #continue the loop as long as i < n
为什么我的代码必须在 .globl
中? .text
的目的是什么?
好的,有一些问题。
更正后的代码在底部。实际上有两种方法可以做到这一点,具体取决于 C 代码的翻译需要达到的字面意思。您的部分概念问题可能是您试图将这两种方法的部分结合起来。
根据评论反馈[重复](例如 li
然后 addi
),您将原始代码中的前两条指令合并为一条。但是,如果只使用一个,li
是正确的,因为 addi
将寄存器添加到自身,但你不能依赖初始值为零。
sll
正在移位一个值为零的寄存器,因此 inst 没有做任何事情。
要用 a0
加载 t1
,您需要使用 add $t1,$a0,0
[或 add $t1,$a0,$zero
]
lw
没有用 [C 代码不加载 a
,为什么要 asm?]。
但是,我稍微改变了一下,因为循环仍然无法正常工作。
在你的 blt
之后没有 return 所以即使循环有效,它也会 "fall off the edge of the world"。每个 called asm 例程 [像 jal set
一样被调用的例程] 必须有一个 explicit return 语句,它是 jr $ra
注意:在 MIPS asm 中,a*
[参数寄存器]可以被被调用者修改,所以循环在 a0
而不是 t1
(即调用者 期望 他们将被丢弃)
无论如何,这是更正后的代码[请原谅不必要的样式清理]:
.text
.globl set
set:
li $t0,0 # i = 0
L1:
sw $a2,0($a0) # a[i] = v
add $a0,$a0,4 # advance pointer
add $t0,$t0,1 # ++i
blt $t0,$a1,L1 # continue the loop as long as i < n
jr $ra # return
如果您的原始 C 函数类似于:
int
set(int *a, int n, int v)
{
int *end;
end = a + n;
for (; a < end; ++a)
*a = v;
return n;
}
那么,这将是一个更直白的翻译:
.text
.globl set
set:
sll $a1,$a1,2 # convert int count to byte length
add $a1,$a0,$a1 # end = a + length
L1:
sw $a2,0($a0) # *a = v
add $a0,$a0,4 # ++a
blt $a0,$a1,L1 # continue the loop as long as a < end
jr $ra # return
IMO,这两种方法都是原始 C 函数的可接受实现。第一个更直白,因为它保留了索引变量 i
的 [概念]。但是,它有一条第二条没有的额外指令。
优化器可能会生成相同的代码(即第二个 asm),而不管它正在翻译哪个 C 函数(MIPS 没有 x86
asm 所具有的强大索引寻址模式)。
所以,"correct" 可能取决于你的教授有多坚持。
旁注:请注意我的两个示例之间的样式变化。也就是说,代码更改放在一边,添加一些空行以提高清晰度。
为了完整起见,这里是我在测试时创建的 main
函数:
.data
arr: .space 400 # allocate more space than count
.text
.globl main
main:
la $a0,arr # get array pointer
li $a1,10 # get count
li $a2,3257 # value to store
jal set
li $v0,1 # exit syscall number
syscall
更新:
If in the loop
a[i++] = v
, would I place theadd $t0, $t0, 1
line before thesw $a2, 0($a0)
?
不,如果 C 代码是 a[++i] = v
,您会这样做。也许,最好的查看方式是先简化 C 代码。
a[i++] = v
实际上是:
a[i] = v;
i += 1;
而且,a[++i] = v
实际上是:
i += 1;
a[i] = v;
现在,C代码行和asm指令一一对应了。
When would I use the
sll
? I was reading examples and I noticed people usually dosll $t1, $t0, 2
when they're going to use a counter to go through an array.
是的。如果您仔细查看我的 second 实现,它会以这种方式使用 sll
。此外,即使给出原始 C 代码,这也是我编写循环代码的方式。
Would I use the
lw
if the C code said something likeint x = a[0]
?
是的,完全正确。
另一种制作 asm 原型的方法是将 C 代码转换为 "very dumb C".
也就是说,只有 if
最简单的形式:if (x >= y) goto label
。甚至 if (x < y) j = 10
也是禁区。
没有函数作用域变量或函数参数变量——只有作为寄存器名称的全局变量。
没有复杂的表达。只有简单的 x = y
、x += y
或 x = y + z
。因此,a = b + c + d
太复杂了。
注册变量同时用作整数值和字节指针。因此,当添加到用作指针的寄存器时,就像添加到字节指针一样,因此要递增 int
数组,您必须添加 4
.
字节指针和 int
指针之间的实际区别仅在您执行 load/store 操作时才重要:lw/sw
用于 int
和 lb/sb
用于字节。
所以,这是我的第二个 C 函数,重新编码为 "dumb":
// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{
v0 = a1; // set return value
a1 <<= 2; // convert int count to byte length
a1 += a0; // point to one past end of array
L1:
*(int *)a0 = a2; // store v at current array location
a0 += 4; // point to next array element
if (a0 < a1) goto L1; // loop back until done
return;
}
更新#2:
In your first implementation, is
add $a0, $a0, 4
the equivalent to usingsll
in the second implementation?
不完全是。要记住的关键是在 C 中,当我们将 1 添加到指针 [或使用 i
对其进行索引时,编译器将生成一条 increment/add 指令,该指令将 sizeof
添加到 type 指针定义为。
也就是说,对于int *iptr
,指定iptr += 1
将生成add $a0,$a0,4
,因为sizeof(int)
是4。如果我们有double *dptr
,dptr += 1
, 编译器会生成 add $a0,$a0,8
因为 sizeof(double)
是 8
这是 C 编译器提供的一个强大的"convenience",因为它允许数组、指针、索引可以互换使用。
在 asm 中,我们必须手动完成 C 编译器会自动为我们完成的工作。
考虑以下情况:我们有一个值,它是数组中 个元素 的计数,称它为 count
。现在,我们想知道数组将占用多少字节。我们称之为 len
。这是用于确定各种类型的 C 代码:
char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
// sll $a1,$a1,0
short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
// sll $a1,$a1,1
int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
// sll $a1,$a1,2
double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
// sll $a1,$a1,3
From what I understand, using sll sets i as a counter for ints so it increments i and also iterates the array
没有。 sll
只是 MIPS 的 "shift left logical" 指令,当您需要 C 的 <<
运算符时可以使用它。
你在想的是sll
如何使用来达到那个效果。
为了遍历 int
的数组,我们将 index 递增 1,但我们还必须将数组指针递增 4。这就是我的第一个 asm例子做到了。终止条件为index >= count
.
在我的第二个 asm 示例中,我通过将元素计数转换为字节长度(通过 ssl
)并添加数组地址来消除单独的索引变量。现在 $a1
得到了数组最后一个元素的地址 + 1,终止条件是 current_address >= last_element_plus_1
。请注意,current_address ($a0
) 仍然必须递增 4 (add $a0,$a0,4
)
要记住的一件重要事情是 asm 指令 [特别是 MIPS] 很简单(即 dumb)。他们一次只做一件事。如果语句足够复杂,单个 C 赋值语句可以生成大约 20 条指令。 asm 指令的组合方式产生了更复杂的结果。