Podczas kompilowania kodu C i patrzenia na asembler wszystkie stosy rosną do tyłu w następujący sposób:
_main:
pushq %rbp
movl $5, -4(%rbp)
popq %rbp
ret
-4(%rbp)
- Czy to oznacza, że wskaźnik bazowy lub wskaźnik stosu przesuwają się w dół po adresach pamięci zamiast w górę? Dlaczego?
Zmieniłem $5, -4(%rbp)
się $5, +4(%rbp)
, opracowano i uruchomiono kod i nie było żadnych błędów. Dlaczego więc wciąż musimy cofać się w stosie pamięci?
-4(%rbp)
ogóle nie przesuwa on wskaźnika bazowego i że+4(%rbp)
to nie mogło działać.malloc
rośnie sterty do tyłumain
blokuje jego RBP. To z pewnością możliwe. (I tak, pisanie4(%rbp)
opierałoby się na zapisanej wartości RBP). Brawo, ten główny nigdy tak nie jestmov %rsp, %rbp
, więc dostęp do pamięci jest względny w stosunku do RBP dzwoniącego , jeśli tak właśnie testował OP !!! Jeśli zostało to faktycznie skopiowane z danych wyjściowych kompilatora, niektóre instrukcje zostały pominięte!Odpowiedzi:
Tak,
push
instrukcje zmniejszają wskaźnik stosu i zapisują na stosie, podczas gdypop
czynność odwrotna odczytuje ze stosu i zwiększa wskaźnik stosu.Jest to nieco historyczne, ponieważ dla maszyn z ograniczoną pamięcią stos został umieszczony wysoko i wyrósł w dół, podczas gdy stos został umieszczony nisko i wyrósł w górę. Jest tylko jedna luka „wolnej pamięci” - pomiędzy stertą i stosem, i ta luka jest wspólna, każda z nich może urosnąć do luki według indywidualnych potrzeb. W ten sposób programowi zabraknie pamięci tylko wtedy, gdy stos i sterty zderzą się, nie pozostawiając wolnej pamięci.
Jeśli zarówno stos, jak i sterty rosną w tym samym kierunku, wówczas istnieją dwie luki, a stos nie może tak naprawdę wyrosnąć w szczelinę sterty (na odwrót również problematyczne).
Początkowo procesory nie miały dedykowanych instrukcji obsługi stosu. Ponieważ jednak obsługa sprzętu została dodana do sprzętu, wzrósł on w dół, a procesory nadal podążają za tym wzorem.
Można argumentować, że na 64-bitowej maszynie jest wystarczająca przestrzeń adresowa, aby pozwolić na wiele luk - i jako dowód, wiele luk koniecznie ma miejsce, gdy proces ma wiele wątków. Chociaż nie jest to wystarczająca motywacja do zmiany sytuacji, ponieważ w przypadku systemów z wieloma przerwami kierunek wzrostu jest prawdopodobnie arbitralny, więc tradycja / zgodność przechyla skalę.
Trzeba by zmienić CPU instrukcje obsługi stosu w celu zmiany kierunku stosu, albo zrezygnować z wykorzystaniem dedykowanych popychanie i popping instrukcje (np
push
,pop
,call
,ret
, inne).Zauważ, że architektura zestawu instrukcji MIPS nie ma dedykowanego
push
&pop
, więc praktyczne jest powiększanie stosu w dowolnym kierunku - nadal możesz chcieć mieć układ pamięci o pojedynczej przerwie dla procesu z jednym wątkiem, ale możesz zwiększyć stos i górę stosu ku dołowi. Jednak jeśli to zrobisz, niektóre kody C varargs mogą wymagać dostosowania w źródle lub w przekazywaniu parametrów pod maską.(W rzeczywistości, ponieważ nie ma dedykowanej obsługi stosu w MIPS, możemy użyć przyrostu wstępnego lub końcowego lub zmniejszenia wstępnego lub końcowego w celu wypychania na stos, pod warunkiem, że użyjemy dokładnej odwrotności do zrzucenia stosu, a także zakładając, że system operacyjny przestrzega wybranego modelu wykorzystania stosu. Rzeczywiście, w niektórych systemach wbudowanych i niektórych systemach edukacyjnych stos MIPS jest zwiększany).
źródło
push
ipop
na większości architektur, ale także o wiele ważniejsze przerwaniami manipulacja,call
,ret
, i co tam jeszcze jest upieczone w interakcji ze stosu.gets()
, lub wykonuje takie,strcpy()
które nie jest wstawiane, wówczas zwrot w tych funkcjach biblioteki użyje zastąpionego adresu zwrotnego. Obecnie z rosnącymi stosami, kiedy wraca ich rozmówca.strcpy
), Na łuku, w którym adres zwrotny jest przechowywany w rejestrze, chyba że trzeba go rozlać, nie ma dostępu do clobber powrotu adres.W twoim systemie stos zaczyna się od adresu o wysokiej pamięci i „rośnie” w dół do adresów o niskiej pamięci. (istnieje również symetryczny przypadek od niskiego do wysokiego)
A ponieważ zmieniłeś z -4 i +4 i działało, to nie znaczy, że jest poprawne. Układ pamięci uruchomionego programu jest bardziej złożony i zależy od wielu innych czynników, które mogą przyczynić się do tego, że nie od razu zawiesiłeś się na tym niezwykle prostym programie.
źródło
Wskaźnik stosu wskazuje granicę między przydzieloną i nieprzydzieloną pamięcią stosu. Zwiększenie go w dół oznacza, że wskazuje on początek pierwszej struktury w przydzielonej przestrzeni stosu, a inne przydzielone elementy następują pod większymi adresami. Posiadanie wskaźników wskazujących na początek przydzielonych struktur jest znacznie częstsze niż na odwrót.
Obecnie w wielu systemach istnieje osobny rejestr ramek stosu , który można nieco niezawodnie rozwinąć w celu ustalenia łańcucha połączeń, z lokalną zmienną pamięcią. Sposób, w jaki rejestr ramki stosu jest skonfigurowany w niektórych architekturach, oznacza, że kończy się wskazywaniem za lokalną pamięcią zmiennych, w przeciwieństwie do wskaźnika stosu przed nią. Zatem użycie tego rejestru ramki stosu wymaga ujemnego indeksowania.
Zauważ, że ramki stosu i ich indeksowanie są bocznym aspektem skompilowanych języków komputerowych, więc to generator kodu kompilatora ma do czynienia z „nienaturalnością”, a nie słabym programistą języka asemblera.
Więc chociaż istnieją dobre historyczne powody, aby wybierać stosy, aby rosły w dół (a niektóre z nich zostały zachowane, jeśli programujesz w języku asemblera i nie zawracasz sobie głowy ustawieniem odpowiedniej ramki stosu), stały się one mniej widoczne.
źródło