好的,有一些问题。
更正后的代码在底部。实际上有两种方法可以做到这一点,具体取决于 C 代码的翻译需要的文字程度。您的部分概念问题可能是您试图将这两种方法的部分结合起来。
您根据评论反馈[重复](例如li
then addi
)将原始代码中的前两条指令合并为一条。但是,如果只使用一个,这li
是正确的,因为addi
将寄存器添加到自身,但您不能依赖初始值为零。
sll
正在移动一个其中具有零值的寄存器,因此 inst 没有做任何事情。
要加载t1
,a0
您可以使用add $t1,$a0,0
[or add $t1,$a0,$zero
]
无用lw
[C 代码不加载a
,为什么要 asm?]。
但是,我改变了这一切,因为循环仍然无法正常工作。
在你之后没有回报,blt
所以即使循环有效,它也会“从世界的边缘掉下来”。每个被调用jal set
的asm例程 [一个像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 所具有的强大的索引寻址模式)。
所以,哪个是“正确的”可能取决于你的教授有多坚持。
旁注:请注意我的两个示例之间的样式变化。也就是说,除了代码更改之外,添加一些空白行以提高清晰度。
为了完整起见,这是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
更新:
如果在循环a[i++] = v
中,我会将add $t0, $t0, 1
行放在前面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 指令之间存在一一对应的关系。
我什么时候使用sll
?我正在阅读示例,我注意到人们通常sll $t1, $t0, 2
在使用计数器遍历数组时会这样做。
是的。如果您仔细查看我的第二个实现,它会sll
以这种方式使用。此外,即使给定原始 C 代码,这也是我编写循环的方式。
lw
如果 C 代码说类似的话,我会使用int x = a[0]
吗?
对,就是这样。
另一种制作 asm 原型的方法是将 C 代码转换为“非常愚蠢的 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
仅在您执行加载/存储操作时才重要:lw/sw
forint
和lb/sb
for byte。
所以,这是我的第二个 C 函数重新编码为“哑”:
// 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:
在您的第一个实现中,是否add $a0, $a0, 4
等同sll
于在第二个实现中使用?
不完全的。要记住的关键是在 C 中,当我们向指针添加 1 [或用 索引它i
] 时,编译器将生成一个递增/添加指令,该指令将指针定义为 sizeof
的类型添加。
也就是说,对于int *iptr
,指定iptr += 1
将生成add $a0,$a0,4
因为sizeof(int)
是 4。如果我们有double *dptr
,dptr += 1
编译器将生成add $a0,$a0,8
因为sizeof(double)
是 8
这是 C 编译器提供的强大“便利”,因为它允许数组、指针、索引可以互换使用。
在 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
据我了解,使用 sll 将 i 设置为整数的计数器,因此它增加 i 并迭代数组
不sll
,仅仅是 MIPS 的“逻辑左移”指令,当你需要相当于 C 的<<
运算符时,你可以使用它。
您正在考虑的是如何sll
使用来实现该效果。
要遍历 的数组int
,我们将索引增加 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] 很简单(即哑)。他们一次只做一件事。如果语句足够复杂,一条 C 赋值语句可以生成大约 20 条指令。组合 asm 指令的方式会产生更复杂的结果。