Рекурсивная сборка Фибоначчи
Сегодня я написал рекурсивные фибоначчи в сборке, и это не работает. Я скомпилировал его в объектный файл с помощью NASM, а затем сделал его с помощью gcc.
Когда я ввожу 1 или 2, функция работает отлично, но когда я ввожу 3, 4, 5, 6 или более, функция не работает. Я думаю, что есть проблема, когда функция вызывает себя.
Это код:
SECTION .data ;init data
str: db "This equal: %d",10,0
SECTION .text ;asm code
extern printf
global main
main:
push ebp
mov ebp,esp
;--------------------
push 03 ; the index
call _fibonacci
add esp,4
push DWORD eax
push str
call printf
;---------------------
mov esp,ebp
pop ebp
ret
Это функция:
_fibonacci:
push ebp
mov ebp,esp
mov ebx, [ebp+8] ;; param n
cmp ebx,0
jne CHECK2
mov eax,0
jmp _endFIbofunc
CHECK2:
cmp ebx,0x1
jne ELSE3
mov eax,1
jmp _endFIbofunc
ELSE3:
mov ebx,[ebp+8]
dec ebx ;; n-1
;; FIRST call
push ebx
call _fibonacci
add esp,4
mov edx,eax
;; SEC CALL
dec ebx
push ebx
call _fibonacci
add esp,4
add eax,edx
mov eax,[ebp-4]
_endFIbofunc:
mov esp,ebp
pop ebp
ret
После того, как я запустил его в Ubuntu 16.04, он отправляет ошибку:
Ошибка сегментации (ядро сброшено)
В чем может быть проблема?
3 ответа
mov eax,[ebp-4]
Вы используете память в [ebp-4]
не вложив в это что-то полезное! Вам необходимо зарезервировать это место в прологе вашей функции:
_fibonacci:
push ebp
mov ebp, esp
sub esp, 4
Возвращаясь с 1-го рекурсивного вызова, вы положили результат из EAX
в этой памяти мечта.
Возврат со второго рекурсивного вызова, к которому вы добавляете EAX
содержание этой памяти мечта.
Делая так, EDX
Регистрация больше не будет засорена.
Почему вы используете EBX
зарегистрироваться вообще? Если вы используете его, вы должны сохранить его, как объяснил в ответе Al Kepp.
Если вы начнете, положив аргумент в EAX
Вы знаете, что для обоих значений ниже 2 (т. е. 0 и 1) результат просто равен аргументу. Просто.
mov eax, [ebp+8] ;; param n
cmp eax, 2
jb _endFIbofunc
Если вы не уравновешиваете стек непосредственно после 1-го рекурсивного вызова, вы можете просто уменьшить значение уже существующего меча и сделать свой второй рекурсивный вызов.
dec eax ; n-1
push eax ;(*)
call _fibonacci
mov [ebp-4], eax
dec dword ptr [esp] ; n-2
call _fibonacci
add esp,4 ;(*)
add eax, [ebp-4]
Весь процесс:
_fibonacci:
push ebp
mov ebp, esp
sub esp, 4 ;(*)
mov eax, [ebp+8] ;; param n
cmp eax, 2
jb _endFIbofunc
dec eax ; n-1
push eax ;(*)
call _fibonacci
mov [ebp-4], eax
dec dword ptr [esp] ;(*) n-2
call _fibonacci
add esp,4 ;(*)
add eax, [ebp-4]
_endFIbofunc:
mov esp, ebp
pop ebp
ret
В дополнение к другим предоставленным ответам, вот альтернативное решение:
_fibonacci:
mov eax,[esp+4] ;eax = n
cmp eax,2 ;br if n < 2
jb _endFIbofunc
dec eax ;push n-1
push eax
call _fibonacci ;returns eax = fib(n-1)
xchg eax,[esp] ;eax = n-1, [esp] = fib(n-1)
dec eax ;push n-2
push eax
call _fibonacci ;returns eax = fib(n-2)
add eax,[esp+4] ;eax = fib(n-1)+fib(n-2)
add esp,8
_endFIbofunc:
ret
Мелочи - фиб (47) самый большой < 2^32. Количество рекурсивных вызовов = 2*fib(n+1)-1.
n fib(n) # calls
0 0 1
1 1 1
2 1 3
3 2 5
4 3 9
5 5 15
6 8 25
7 13 41
8 21 67
9 34 109
10 55 177
11 89 287
12 144 465
13 233 753
14 377 1219
15 610 1973
16 987 3193
17 1597 5167
18 2584 8361
19 4181 13529
20 6765 21891
21 10946 35421
22 17711 57313
23 28657 92735
24 46368 150049
25 75025 242785
26 121393 392835
27 196418 635621
28 317811 1028457
29 514229 1664079
30 832040 2692537
31 1346269 4356617
32 2178309 7049155
33 3524578 11405773
34 5702887 18454929
35 9227465 29860703
36 14930352 48315633
37 24157817 78176337
38 39088169 126491971
39 63245986 204668309
40 102334155 331160281
41 165580141 535828591
42 267914296 866988873
43 433494437 1402817465
44 701408733 2269806339
45 1134903170 3672623805
46 1836311903 5942430145
47 2971215073 9615053951
48 4807526976 ...
Вы должны сохранить (протолкнуть) регистры, которые вы собираетесь изменить в рекурсивном вызове. А затем восстановить их первоначальные значения (поп). Это должно решить проблему.
Что-то вроде этого:
- Нажмите на все регистры, которые вы собираетесь использовать в своей функции (кроме eax, где вы получите возвращаемое значение)
- Нажмите ebx, поскольку это ваш параметр
- Вызвать функцию
- Добавить esp,4
- Вставьте все регистры, которые вы нажали на первом шаге, теперь в обратном порядке