MIPS 程序集:为什么 main 可以在不释放其堆栈 space 的情况下退出?
MIPS assembly: Why can main exit without deallocating its stack space?
我有一道大学习题,我不懂。我们必须将 C 语言转换为汇编 MIPS。主要是我必须为 a[100]
向量分配 400 个字节,但在解决方案中我的教授没有在函数末尾释放它,为什么会这样?是否存在不需要释放内存移动堆栈指针的情况?
这是 C 中的代码:
int idamax(int n, float * dx, int incx) {
float dmax;
int i, ix, itemp;
if (n < 1) return (-1);
if (n == 1) return (0);
if (incx != 1) {
ix = 1;
dmax = fabs(dx[0]);
ix = ix + incx;
for (i = 1; i < n; i++) {
if (dmax < fabs(dx[ix])) {
itemp = i;
dmax = fabs(dx[ix]);
}
ix = ix + incx;
}
} else {
itemp = 0;
dmax = fabs(dx[0]);
for (i = 1; i < n; i++) {
if (dmax < fabs(dx[i])) {
itemp = i;
dmax = fabs(dx[i]);
}
}
}
return (itemp);
}
int main() {
float a[100];
int l, k, n = 100, lda = 10;
for (k = 0; k < n; ++k) a[k] = (float)((k * k * k) % 100);
k = 4;
l = idamax(n - lda * k - k, &a[lda * k + k], 1) + k;
print_int(l);
exit;
}
主要汇编代码:
main:
#______CALL_FRAME______
# 100 float: 400B
#______Totale 400B
addi $sp,$sp,-400
add $t9,$sp,[=12=] #&a
addi $t0, [=12=], 100 #n=100
addi $t1, [=12=], 10 #lda=10
#l in t2, k in t3
add $t3, [=12=], [=12=] #k=0
main_forini:
slt $t5,$t3,$t0 #k<?n
beq $t5,[=12=],main_forend
mult $t3, $t3 #k*k
mflo $t5
mult $t3, $t5
mflo $t5 #k*k*k
div $t5,$t0 #()%n
mfhi $t5
mtc1 $t5,$f0
cvt.s.w $f1,$f0 #(float)()
sll $t5,$t3,2 #k*4
add $t5,$t5,$t9 #&a[k]
swc1 $f1,0($t5) #a[k]=()
addi $t3, $t3, 1 #++k
j main_forini
main_forend:
addi $t3,[=12=],4 #k=4
mult $t1,$t3 #lda*k
mflo $t5
add $t5,$t5,$t3 #lda*k+k
sub $a0,$t0,$t5 #a0=n-lda*k-k
sll $t5,$t5,2
add $a1,$t5,$t9 #a1=&a[lda*k+k]
addi $a2,[=12=],1 #a2=1
jal idamax
addi $a0,$v0,4 #a0=l=retval+k
addi $v0,[=12=],1 #print_int
syscall
addi $v0,[=12=],10 #exit
syscall
这里有两个可能的答案。
第一个答案是main
是你程序的第一个也是最后一个函数。 OS 之后会进行清理。
第二个答案是针对使用堆栈内存的其他函数。堆栈内存通常通过恢复调用函数的堆栈帧来释放(main
没有,因此例外)。
main
的执行永远不会到达函数的底部,因此永远不需要清理堆栈; exit()
是一个 "noreturn" 函数。
如果 main
did 想要 return 和 jr $ra
而不是进行 exit
系统调用,您需要恢复堆栈指针以及其他调用保留寄存器。否则,您将违反 main
的调用者希望 main
遵循的调用约定。
(已更新,因为您将 asm 添加到使用 MARS 系统调用的问题中:如果 main
位于代码顶部,则它可能不是函数:$ra
不是有效的return 入口地址,所以它不能 return。如果它不是函数,IMO 不要称它为 main
。)
当进程退出系统调用时,OS 不关心 user-space 堆栈指针指向何处,因此不需要 main
退出前进行清理。
(在 "normal" C 实现中,exit()
函数将编译为 jal exit
或简单的尾调用 j exit
。但是您正在手动编译没有 C 库的 MARS 模拟器,所以你内联系统调用而不是调用包装函数。)
还要注意 ISO C exit(int)
需要一个参数,例如 MARS exit2
(syscall
/$v0=17
)。事实上,您甚至没有将 exit()
作为函数调用,您只是在 C 中编写了 exit;
,它将 exit
计算为函数指针,而无需调用它或对该值执行任何操作。
通常 C main
由 CRT 启动代码调用,例如 运行 C 库初始化函数并将 argc 和 argv[] 指针放入正确的寄存器中。所以 main
通常不是来自 OS 的实际进程入口点,尤其是在托管实现中。 (即编译的 C 程序 运行 在 OS 下,而不是像独立程序一样是它们自己的内核。)
如果您只是为 MARS 或 SPIM 模拟器或其他东西翻译它,那么没有 C 库或任何超出您编写的代码,所以您正在编写的是通常情况下被称为 _start
,而不是 main
.
在 C 中 main
是函数,但在 MARS 中 you can't jr $ra
from the top-level entry point 所以 入口点不是函数。因此不要称它为 main
.
在 ISO C 中,main
递归调用自身或其他函数调用 main
甚至是合法的。这只有在 main 确实是一个可以正确清理堆栈和 returns 的函数时才有效。但这意味着它不能同时是需要进行 exit
系统调用的进程入口点。对于 运行 一个具有疯狂递归 main 的程序,最终执行 C return
语句(或脱离 main 的末尾),main 几乎必须编译为一个真正的函数,可以 return 和 jr $ra
。所以它必须是一个函数,你 jal main
从你的 _start
入口点。
我有一道大学习题,我不懂。我们必须将 C 语言转换为汇编 MIPS。主要是我必须为 a[100]
向量分配 400 个字节,但在解决方案中我的教授没有在函数末尾释放它,为什么会这样?是否存在不需要释放内存移动堆栈指针的情况?
这是 C 中的代码:
int idamax(int n, float * dx, int incx) {
float dmax;
int i, ix, itemp;
if (n < 1) return (-1);
if (n == 1) return (0);
if (incx != 1) {
ix = 1;
dmax = fabs(dx[0]);
ix = ix + incx;
for (i = 1; i < n; i++) {
if (dmax < fabs(dx[ix])) {
itemp = i;
dmax = fabs(dx[ix]);
}
ix = ix + incx;
}
} else {
itemp = 0;
dmax = fabs(dx[0]);
for (i = 1; i < n; i++) {
if (dmax < fabs(dx[i])) {
itemp = i;
dmax = fabs(dx[i]);
}
}
}
return (itemp);
}
int main() {
float a[100];
int l, k, n = 100, lda = 10;
for (k = 0; k < n; ++k) a[k] = (float)((k * k * k) % 100);
k = 4;
l = idamax(n - lda * k - k, &a[lda * k + k], 1) + k;
print_int(l);
exit;
}
主要汇编代码:
main:
#______CALL_FRAME______
# 100 float: 400B
#______Totale 400B
addi $sp,$sp,-400
add $t9,$sp,[=12=] #&a
addi $t0, [=12=], 100 #n=100
addi $t1, [=12=], 10 #lda=10
#l in t2, k in t3
add $t3, [=12=], [=12=] #k=0
main_forini:
slt $t5,$t3,$t0 #k<?n
beq $t5,[=12=],main_forend
mult $t3, $t3 #k*k
mflo $t5
mult $t3, $t5
mflo $t5 #k*k*k
div $t5,$t0 #()%n
mfhi $t5
mtc1 $t5,$f0
cvt.s.w $f1,$f0 #(float)()
sll $t5,$t3,2 #k*4
add $t5,$t5,$t9 #&a[k]
swc1 $f1,0($t5) #a[k]=()
addi $t3, $t3, 1 #++k
j main_forini
main_forend:
addi $t3,[=12=],4 #k=4
mult $t1,$t3 #lda*k
mflo $t5
add $t5,$t5,$t3 #lda*k+k
sub $a0,$t0,$t5 #a0=n-lda*k-k
sll $t5,$t5,2
add $a1,$t5,$t9 #a1=&a[lda*k+k]
addi $a2,[=12=],1 #a2=1
jal idamax
addi $a0,$v0,4 #a0=l=retval+k
addi $v0,[=12=],1 #print_int
syscall
addi $v0,[=12=],10 #exit
syscall
这里有两个可能的答案。
第一个答案是main
是你程序的第一个也是最后一个函数。 OS 之后会进行清理。
第二个答案是针对使用堆栈内存的其他函数。堆栈内存通常通过恢复调用函数的堆栈帧来释放(main
没有,因此例外)。
main
的执行永远不会到达函数的底部,因此永远不需要清理堆栈; exit()
是一个 "noreturn" 函数。
如果 main
did 想要 return 和 jr $ra
而不是进行 exit
系统调用,您需要恢复堆栈指针以及其他调用保留寄存器。否则,您将违反 main
的调用者希望 main
遵循的调用约定。
(已更新,因为您将 asm 添加到使用 MARS 系统调用的问题中:如果 main
位于代码顶部,则它可能不是函数:$ra
不是有效的return 入口地址,所以它不能 return。如果它不是函数,IMO 不要称它为 main
。)
当进程退出系统调用时,OS 不关心 user-space 堆栈指针指向何处,因此不需要 main
退出前进行清理。
(在 "normal" C 实现中,exit()
函数将编译为 jal exit
或简单的尾调用 j exit
。但是您正在手动编译没有 C 库的 MARS 模拟器,所以你内联系统调用而不是调用包装函数。)
还要注意 ISO C exit(int)
需要一个参数,例如 MARS exit2
(syscall
/$v0=17
)。事实上,您甚至没有将 exit()
作为函数调用,您只是在 C 中编写了 exit;
,它将 exit
计算为函数指针,而无需调用它或对该值执行任何操作。
通常 C main
由 CRT 启动代码调用,例如 运行 C 库初始化函数并将 argc 和 argv[] 指针放入正确的寄存器中。所以 main
通常不是来自 OS 的实际进程入口点,尤其是在托管实现中。 (即编译的 C 程序 运行 在 OS 下,而不是像独立程序一样是它们自己的内核。)
如果您只是为 MARS 或 SPIM 模拟器或其他东西翻译它,那么没有 C 库或任何超出您编写的代码,所以您正在编写的是通常情况下被称为 _start
,而不是 main
.
在 C 中 main
是函数,但在 MARS 中 you can't jr $ra
from the top-level entry point 所以 入口点不是函数。因此不要称它为 main
.
在 ISO C 中,main
递归调用自身或其他函数调用 main
甚至是合法的。这只有在 main 确实是一个可以正确清理堆栈和 returns 的函数时才有效。但这意味着它不能同时是需要进行 exit
系统调用的进程入口点。对于 运行 一个具有疯狂递归 main 的程序,最终执行 C return
语句(或脱离 main 的末尾),main 几乎必须编译为一个真正的函数,可以 return 和 jr $ra
。所以它必须是一个函数,你 jal main
从你的 _start
入口点。