Сборка MIPS: Почему основной выход может быть освобожден без освобождения пространства стека? - PullRequest
0 голосов
/ 24 марта 2020

У меня вопрос к университетскому занятию, которого я не понимаю. Мы должны перевести с C на сборку MIPS. В основном я должен выделить 400 байтов для вектора a[100], но в решениях мой профессор не освобождает его в конце функции, почему это происходит? Есть ли случаи, когда мне не нужно освобождать указатель стека перемещения памяти?

Вот код в 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,$0 #&a
 addi $t0, $0, 100 #n=100
 addi $t1, $0, 10 #lda=10
#l in t2, k in t3

 add $t3, $0, $0 #k=0
main_forini:
 slt $t5,$t3,$t0 #k<?n
 beq $t5,$0,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,$0,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,$0,1 #a2=1
 jal idamax
 addi $a0,$v0,4 #a0=l=retval+k
 addi $v0,$0,1 #print_int
 syscall
 addi $v0,$0,10 #exit
 syscall

Ответы [ 2 ]

2 голосов
/ 24 марта 2020

Выполнение main никогда не достигает нижней части функции, поэтому очистка стека никогда не должна происходить; exit() - это функция «noreturn».

Если main действительно хочет вернуться с jr $ra вместо системного вызова exit, вам необходимо восстановить указатель стека вместе с другими сохраненными вызовами регистрами. В противном случае вы нарушили бы соглашение о вызовах, которое ожидает вызывающий main main.

(Обновлено, поскольку вы добавили asm к вопросу, использующему системный вызов MARS: main возможно, это не функция, если она находится в верхней части вашего кода: $ra не является допустимым адресом возврата в записи, поэтому он не может вернуться. IMO не вызывайте его main, если это не функция.)

Операционная система не заботится о том, куда указывает указатель стека пользовательского пространства, когда процесс выполняет системный вызов выхода, поэтому main не нужно очищать перед выходом.

(В «нормальной» реализации C функция exit() будет компилироваться в jal exit или простой вызов хвоста j exit. Но вы компилируете вручную для симулятора MARS, у которого нет C library, так что вы вызываете системные вызовы вместо вызова функций-оболочек.)

Также обратите внимание, что ISO C exit(int) принимает аргумент, например MARS exit2 (syscall / * 1033) *) . На самом деле вы даже не вызывали exit() как функцию, вы просто написали exit; в C, которая оценивает exit как указатель на функцию, не вызывая ее и ничего не делая с этим значением.


Обычно C main вызывается кодом запуска CRT, который может, например, запускать C функции инициализации библиотеки и помещать arg c и указатель argv [] в правые регистры. Так что main обычно не является реальной точкой входа процесса из ОС, особенно в размещенной реализации. (т.е. скомпилированные C программы работают под ОС, а не являются собственным ядром, как отдельная программа.)

Если вы просто переводите это для симуляторов MARS или SPIM или чего-то еще, то нет C библиотека или любой другой код, кроме того, что вы пишете, , поэтому вы пишете то, что обычно называется _start, а не main.

In C main - это функция, но в MARS вы не можете jr $ra от точки входа верхнего уровня , поэтому точка входа не является функцией . Поэтому не называйте это main.

В ISO C даже main даже законно вызывать себя рекурсивно, или другие функции вызывать main. Это может работать только в том случае, если main действительно является функцией, которая очищает стек и возвращает корректно. Но это означает, что это также не может быть точка входа в процесс, которая должна сделать системный вызов exit. Чтобы запустить программу с безумной рекурсивной функцией main, которая в конечном итоге выполняет оператор C return (или падает с конца main), main в значительной степени должна быть скомпилирована в реальную функцию, которая может возвращаться с jr $ra. Так что это должна быть функция, которую вы jal main выполняете из своей _start точки входа.

0 голосов
/ 24 марта 2020

Здесь возможны два ответа.

Первый ответ заключается в том, что main - это первая и последняя функция вашей программы. После этого ОС очистится.

Второй ответ - для других функций, использующих стековую память. Стековая память обычно освобождается путем восстановления стекового фрейма вызывающей функции (которого у main нет, отсюда исключение).

...