我需要将这个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”和“数据/堆栈读取错误地址: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
的目的是什么?
答案 0 :(得分:1)
好的,有一些问题。
更正后的代码位于底部。实际上有两种方法可以实现,基于C代码的翻译需要的字面值。你的概念问题的一部分可能是你试图结合两种方法的部分。
根据评论反馈[重复](例如li
然后addi
),您将原始代码中的前两个说明折叠为一个。但是,如果只使用一个,li
是正确的,因为addi
会将寄存器添加到自身,但您不能依赖初始值为零。
sll
正在移动一个零值的寄存器,因此inst不会做任何事情。
要使用t1
加载a0
,您需要使用add $t1,$a0,0
[或add $t1,$a0,$zero
]
lw
无用[C代码无法加载a
,为什么要asm?]。
但是,我把这一切都改变了一下,因为循环仍然无法正常工作。
在blt
之后没有回复,所以即使循环有效,它也会“脱离世界的边缘”#34;。每个调用 asm例程[一个像jal set
调用的例程]必须有一个显式返回语句,即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所做的强大的索引寻址模式。
所以,这是&#34;正确&#34;可能取决于你的教授是多么坚固。
旁注:请注意我的两个示例之间的样式更改。也就是说,除了代码更改,添加一些空行以提高清晰度。
为了完整起见,这是我在测试时创建的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代码,它也是我编写循环的方式。
如果C代码说
lw
之类的话,我会使用int x = a[0]
吗?
是的,确实。
另一种原型asm的方法是将C代码转换为非常愚蠢的C&#34;。
即,只有最简单形式的if
:if (x >= y) goto label
。即使if (x < y) j = 10
也不受限制。
没有函数作用域变量或函数参数变量 - 只有作为寄存器名称的全局变量。
没有复杂的表达方式。只有x = y
,x += y
或x = y + z
这样的简单版本。因此,a = b + c + d
太复杂了。
寄存器变量既可以作为整数值,也可以作为 byte 指针。因此,当添加到用作指针的寄存器时,它就像添加到字节指针一样,因此要通过int
数组递增,您必须添加4
。
字节指针和int
指针之间的实际差异仅在执行加载/存储操作时很重要:lw/sw
用于int
,lb/sb
用于字节。
所以,这里我的第二个C函数记录为&#34; dumb&#34;:
// 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中,当我们向指针添加一个[或用i
索引它]时,编译器将生成一个增量/加法指令,用于添加的sizeof
type 指针定义为。
也就是说,对于int *iptr
,指定iptr += 1
会生成add $a0,$a0,4
,因为sizeof(int)
是4.如果我们有double *dptr
,dptr += 1
,编译器会生成add $a0,$a0,8
,因为sizeof(double)
是8
这是一个强大的方便&#34; 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设置为int的计数器,因此它递增i并且还迭代数组
没有。 sll
仅仅是MIPS&#34;&#34;左移逻辑&#34;当您需要相应的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指令产生更复杂的结果的。