Сборка MIPS: почему основной выход может быть освобожден без освобождения места в стеке?

У меня вопрос к университетскому занятию, которого я не понимаю. Мы должны перевести с 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 ответа


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

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

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

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

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

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


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

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

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

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


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

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

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


Есть идеи?

10000