ROZDZIAŁ DWUNASTY PROCEDURY ZAAWANSOWANE TEMATY, INFORMATYKA, „THE ART OF ASSEMBLY LANGUAGE” [PL]

[ Pobierz całość w formacie PDF ]
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
WYŁĄCZNOŚĆ DO PUBLIKOWANIA TEGO TŁUMACZENIA
POSIADA
RAG
WWW.R-AG.PRV.PL
„THE ART OF ASSEMBLY LANGUAGE”
tłumaczone by KREMIK
konsultacja naukowa: NEKRO
wankenob@priv5.onet.pl
nekro@pf.pl
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
ROZDZIAŁ DWUNASTY:
PROCEDURY: ZAAWANSOWANE TEMATY
Ostatni rozdział omawiał jak tworzyć procedury, przekazywać parametry i alokować oraz uzyskać dostęp do
zmiennych lokalnych. Ten rozdział omawia jak uzyskać dostęp w innych procedurach, przekazywanie procedur jako
parametrów i implementacje jakichś zdefiniowanych przez użytkownika struktur sterujących.
12.0 WSTĘP
Rozdział ten całkowicie omawia procedury, parametry i zmienne lokalne rozpoczęte w poprzednim
rozdziale. Rozdział ten opisuje jak języki o strukturze blokowej takie jak Pascal, Odula-2, Algol i Ada uzyskują
dostęp do lokalnych i nielokalnych zmiennych. Omawia również jak zaimplementować zdefiniowane przez
użytkownika strukturę sterującą, iterator. Większość materiału w tym rozdziale będzie interesująca dla piszących
kompilatory i tych którzy chcą nauczyć się jak kompilator generuje kod dla pewnych typów konstrukcji
programowych.. Kilka czystych programów języka asemblera będzie używać technik, które opisuje ten rozdział.
Dlatego niewiele z tego materiału tego rozdziału nie jest szczególnie ważne dla tych ,którzy chcą tylko nauczyć się
języka asemblera. Jednakże, jeśli mamy zamiar pisać kompilatory, lub chcemy zrozumieć jak kompilator generuje
kod, żeby pisać wydajne programy w HLL’ach, będziemy musieli nauczyć się tego materiału wcześniej czy później.
Rozdział ten zaczyna się od omówienia pojęcia
zasięg
i jak HLL’e jak Pascal uzyskują dostęp do
zmiennych w zagnieżdżonych procedurach. Pierwsza sekcja omawia koncepcję zagnieżdżenia leksykalnego i
zastosowania wiązań statycznych i terminali do uzyskania dostępu do zmiennych nielokalnych. Następnie ten
rozdział opisuje jak przekazać zmienne spod różnych poziomów leksykalnych jako parametry. Trzecia sekcja
omawia jak przekazać parametry z jednej procedury jako parametry do innej procedury. Czwarta zasadniczy temat
tego rozdziału opisuje przekazywanie procedur jako parametrów. Rozdział kończy się opisaniem iteratorów,
zdefiniowanej przez użytkownika struktury danych.
Rozdział ten zakłada znajomość języków o strukturze blokowej takich jak Pascal czy Ada. Jeśli nasze
doświadczenia z HLL’ami są związane z językami o strukturze nie blokowej takimi jak C, C++, BASIC czy
FORTRAN, niektóre z koncepcji przedstawianych w tym rozdziale może być zupełnie nowych i możemy mieć
problemy z ich zrozumieniem. Jakiś wstępny tekst o Pascalu lub Adzie bezie pomocny przy wyjaśnieniu
niezrozumiałych koncepcji, które ten rozdział zakłada jako warunek wstępny .
12.1 ZAGNIEŻDŻENIA LEKSYKALNE, ŁĄCZENIA STATYCZNE I DISPLAY
W języku o strukturze blokowej, takim jak Pascal możliwe jest zagnieżdżanie procedur i funkcji.
Zagnieżdżanie jednej procedury wewnątrz innej ogranicza dostęp do zagnieżdżonej procedury; nie możemy uzyskać
dostępu do zagnieżdżonej procedury z zewnątrz otaczającej procedury. Podobnie zmienne zadeklarowane wewnątrz
procedury są widoczne wewnątrz tej procedury i dla wszystkich procedur zagnieżdżonych wewnątrz tej procedury.
Jest to standardowe pojęcie zakresu języka o strukturze blokowej, które powinno być dobrze znane, każdemu kto
pisał programy w Pascalu lub Adzie.
Jest wiele złożoności ukrytej za koncepcją zakresu, lub leksykalnego zagnieżdżenia, w języku o strukturze
blokowej. Podczas gdy dostęp do zmiennych lokalnych w bieżącym rekordzie aktywacji jest wydajny, dostęp do
zmiennych globalnych w języku o strukturze blokowej może być bardzo niewydajny. Sekcja ta będzie opisywać jak
HLL’e jak Pascal zajmują się nielokalnymi identyfikatorami i jak uzyskują dostęp do zmiennych globalnych i
wywołują nielokalne procedury i funkcje.
12.1.1 ZASIĘG
Zasięg w większości języków wysokiego poziomu jest pojęciem statycznym lub wykonywanym w czasie
kompilacji. Zasięg jest pojęciem ,kiedy nazwa jest widzialna lub dostępna wewnątrz programu. Ta zdolność do
ukrywania nazw jest użyteczna w programach ponieważ jest to często dogodne przy wielokrotnym używaniu
pewnych (nie opisowych) nazw. Zmienna i stosowana do sterowania większości pętli for w językach wysokiego
poziomu jest doskonałym przykładem. W całym rozdziale będziemy widzieli coś takiego jak xyz_i, xyz_j itp. Powód
dla wyboru takich nazw jest taki ,że MASM nie wspiera koncepcji zasięgu nazw jak języki wysokiego poziomu. NA
szczęście MASM 6.x i późniejsze wspierają zasięg nazw.
Domyślnie MASM 6.x traktuje etykiety instrukcji (te z dwukropkiem po nich) jako lokalne dla procedury.
To znaczy, możemy tylko odnosić się do takich etykiet wewnątrz procedury w której są zadeklarowane. Jest to
prawda nawet jeśli zagnieździmy jedną procedurę wewnątrz innej. Na szczęście, nie ma dobrego powodu aby chcieć
zagnieżdżać procedury w programie MASM;
Posiadanie lokalnych etykiet wewnątrz procedury jest miłe. Pozwala to nam na ponowne użycie etykiety
instrukcji (np. etykieta pętli) bez martwienia się o konflikt nazw z innymi procedurami. Czasami jednakże, możemy
chcieć wyłączyć zasięg nazw w procedurze; dobrym przykładem jest to kiedy mamy instrukcję case, której tablica
skoków pojawia się na zewnątrz procedury. Jeśli etykiety instrukcji case są lokalne w tej procedurze, nie będą
widzialne na zewnątrz procedury i nie można ich użyć przy tablicy skoków instrukcji case. Są dwa sposoby w jaki
możemy wyłączyć zasięg etykiet w MASM 6.x. Pierwszy sposób to zawarcie instrukcji w naszym programie:
option nonscoped
To pozwoli wyłączyć zasięg zmiennych od tego punktu w przód w naszym pliku źródłowym programu. Możemy z
powrotem włączyć zasięg poprzez instrukcję w postaci
option scoped
Poprzez umieszczenie tych instrukcji wokół naszej procedury możemy wybiórczo sterować zasięgiem.
Inny sposób do sterowania zasięgiem pojedynczych nazw jest umieszczenie podwójnego dwukropka („::”)
po etykiecie. Informuje to asembler ,że ta szczególna nazwa powinna być globalna dla otaczającej procedury.
MASM podobnie jak język C, wspiera trzy poziomy zasięgu: publiczny, globalny (lub statyczny) i lokalny.
Symbole lokalne są widoczne tylko wewnątrz procedury, w której są zdefiniowane. Symbole globalne są dostępne w
całym pliku źródłowym, ale nie są widoczne w innych modułach programu. Symbole publiczne są widoczne w
całym programie, w modułach. MASM używa następujących domyślnych zasad zasięgu:
*Domyślnie etykieta instrukcji pojawia się w procedurze jako lokalna dla tej procedury
*Domyślnie wszystkie nazwy procedur są publiczne
*Domyślnie większość innych symboli jest globalna
Zauważmy, że te zasady dotyczą tylko MASMa 6.x. Inne asemblery i wcześniejsze wersje MASMa korzystają z
różnych zasad.
Przesłonięcie domyślności pierwszej z powyższych zasad jest łatwe - albo zastosujemy instrukcję option
nonscoped albo zastosujemy podwójny dwukropek dla uczynienia globalnej etykiety. Powinniśmy być świadomi, że
nie możemy uczynić lokalnej etykiety publicznej stosując dyrektyw public lub externdef. Możemy uczynić symbol
globalnym (stosując obojętnie jaką technikę) przed uczynieniem ją publiczną.
Posiadanie wszystkich nazw procedur publicznymi domyślnie zazwyczaj nie jest dużym problemem.
Jednakże może się okazać, że chcemy zastosować tą samą (lokalna) nazwę procedury w kilku różnych modułach.
Jeśli MASM automatycznie uczyni takie nazwy publicznymi, linker da nam błąd ponieważ są to wielokrotne
publiczne procedury o tej samej nazwie. Możemy włączyć lub wyłączyć tą domyślną akcję stsoując poniższe
instrukcje:
option proc:private
;procedury są globalne
Rysunek 12. Identyfikacja zasięgu
Option proc:export ;procedury są publiczne
Zauważmy ,że jakieś debuggery tylko dostarczają informacji symbolicznych jeśli nazwa procedury jest publiczna.
Jest tak dlatego, że MASM 6.x domyślnie ustawia je na nazwy publiczne. Problem ten nie istnieje w CodeView;
więc możemy zastosować którykolwiek jest bardziej dogodny. Oczywiście, jeśli wybierzemy prywatne nazwy
procedur (tylko globalne), wtedy będziemy musieli użyć dyrektyw public lub externdef dla uczynienia żądanej
nazwy procedury publiczną.
To omówienie lokalnych, globalnych i publicznych symboli stosuje się głownie do instrukcji i etykiet
procedur. Nie ma zastosowania do zmiennych zadeklarowanych w segmencie danych, przyrównań, makr typedefów
lub większości innych symboli. Takie symbole są zawsze globalne bez względu na to gdzie je zdefiniujemy. Jedyny
sposób uczynienia ich publicznymi jest wyszczególnienie ich nazw dyrektywami public lub externdef
Jest sposób deklaracji nazw parametrów i zmiennych lokalnych, alokowanych na stosie, taki ,że ich nazwy
są lokalne dla danej procedury. Zobacz do dyrektywy proc w podręczniku do MASMa po szczegóły
Zakres nazwy ogranicza swoją widzialność wewnątrz programu. To znaczy, program ma dostęp do nazwy
zmiennej tylko wewnątrz tego zasięgu nazw. Na zewnątrz zasięgu program nie ma dostępu do tej nazwy. Wiele
języków programowania, takich jak Pascal i C++ pozwalają nam na ponowne użycie identyfikatorów jeśli zasięg
tych wielokrotnych użyć nie zachodzi na siebie. Jak widzieliśmy MASM dostarcza minimalnych cech zasięgu
etykiet instrukcji. Jest jednak inna kwestia związana z zasięgiem: powiązanie adresu i czas życia zmiennej.
Powiązanie adresu jest to działanie kojarzenia adresu pamięci z nazwą zmiennej. Czas życia zmiennej jest tą częścią
wykonywanego programu podczas którego komórka pamięci jest ograniczana do zmiennej. Rozważmy poniższą
procedurę Pascalowską:
Rysunek 12.1 pokazuje zasięg identyfikatorów One, Two, Entry, i , j i Param.
Lokalna zmienna j w Two maskuje identyfikator j w procedurze One wewnątrz Two
12.1.2 AKTYWACJA ELEMENTU, POWIĄZANIE ADRESU I CZAS ŻYCIA ZMIENNEJ
Aktywacja elementu jest procesem wywoływania procedury lub funkcji. Kombinacja rekordu aktywacji i
jakiegoś kodu wykonywalnego jest uważane za przypadek podprogramu. Kiedy występuje aktywacja elementu
podprogram wiąże adres maszynowy do swoich lokalnych zmiennych. Adres powiązany (dla zmiennych lokalnych)
występuje wtedy kiedy podprogram modyfikuje wskaźnik stosu aby zrobić miejsce dla zmiennych lokalnych. Czas
życia tych zmiennych jest od tego punktu do momentu kiedy podprogram niszczy rekord aktywacji eliminując
pamięć dla zmiennych lokalnych.
Chociaż zasięg ogranicza widzialność nazw do pewnej części kodu i nie pozwala na powtarzanie nazw
wewnątrz tego samego zasięgu, nie znaczy to, że jest tylko jeden adres graniczny dla nazwy. Jest całkiem możliwe,
że ma kilka adresów granicznych dla tej samej nazwy w tym samym czasie. Rozważmy wywołanie rekurencyjne
procedury. Przy każdej aktywacji procedura buduje nowy rekord aktywacji. Ponieważ poprzednia instancja jeszcze
istnieje , teraz są dwa rekordy aktywacji na stosie zawierające zmienne lokalne dla tej procedury. Ponieważ
dodatkowo wystąpiła rekurencyjna aktywacja, system buduje więcej rekordów aktywacji, każdy z adresem
granicznym do tej samej nazwy. Rozwiązanie tej możliwej dwuznaczności (który adres jest dostępny kiedy działamy
na zmiennej?), system zawsze manipuluje zmienną w ostatnim rekordzie aktywacji.
Zauważmy, że procedury One i Two w poprzedniej sekcji są pośrednio rekurencyjne. To znaczy, obie
wywołują podprogramy które ,po kolei , wywołuje je same. Zakładając, że parametr One jest mniejszy niż 10 przy
inicjalizacji wywołania, kod ten będzie generował wiele rekordów aktywacji ( i dlatego też wiele kopii zmiennych
lokalnych) na stosie. Na przykład, mamy wywołanie One(9),wtedy stos wygląda tak jak na rysunku 12.2 po
pierwszym napotkaniu end związanego z procedurą Two.
Jak możemy zobaczyć, jest kilka kopii I i J na stosie w tym punkcie. Procedura Two (bieżący wykonywany
podprogram) uzyskuje dostęp do J w ostatnim rekordzie aktywacji, który jest na samym dole rysunku 12.2.
Poprzednia instancja Two uzyskiwała dostęp do zmiennej J tylko w swoim rekordzie aktywacji, kiedy bieżąca
instancja wracała do One a potem cofała do Two.
Czas życia instancji zmiennych jest od punktu stworzenia rekordu aktywacji do punktu usunięcia rekordu
aktywacji. Zauważmy ,że pierwsza instancja J ,powyżej, (na szczycie powyższego diagramu) ma najdłuższy czas
życia ze wszystkich zachodzących na siebie instancji J.
12.1.3 ŁĄCZENIA STATYCZNE
Pascal pozwala procedurze Two uzyskać dostęp do I w procedurze One. Jednakże, kiedy istnieje możliwość
rekurencji może być kilka instancji I na stosie. Pascal, oczywiście, pozwoli tylko procedurze Two na dostęp do
ostatniej instancji I. Na diagramie stosu na rysunku 12.2, odpowiada to wartości I w rekordzie aktywacji, który
zaczyna się z „parametrem One(9+1)”. Jedynym problemem jest to „jak się dowiedzieć gdzie znajduje się rekord
aktywacji zawierający I”?
Szybką ale kiepską myślą, jest odpowiedź, że jest to po prostu indeks wsteczny do stosu. W końcu możemy
łatwo zobaczyć na powyższym diagramie, że I jest offsetem osiem z rekordu aktywacji Two. Niestety, nie zawsze
jest taki przypadek. Zakładamy, że procedura Three również wywołuje procedurę Two a poniższa instrukcja pojawia
się wewnątrz procedury One:
If (Entry <5) then Three(Entry*2) else Two(Entry);
Z tą instrukcją w tym miejscu jest całkiem możliwe jest posiadanie dwóch różnych ramek stosu na wejściu do
Procedury Two: jeden z rekordem aktywacji dla procedury Three wciśnięty między rekordy aktywacji One i Two i
jeden z rekordami aktywacji dla procedur One i Two przylegającymi jeden do drugiego. Najwyraźniej stały offset z
rekordu aktywacji Two nie zawsze wskazuje na zmienną I w ostatnim rekordzie aktywacji.
Rysunek 12.2 Pośrednia rekurencja
Przebiegły czytelnik może zauważyć, że zachowana wartość bp w rekordzie aktywacji Two wskazuje na
wywołujący rekord aktywacji. Można pomyśleć, że możemy użyć tego jako wskaźnika do rekordu aktywacji One.
Ale ten schemat jest zawodny z tego samego powodu że stały offset zawodzi technicznie. A stara wartość Bp,
łączona dynamicznie, wskazuje na wywołujący rekord aktywacji. Ponieważ kod wywołujący nie jest konieczni
otoczony procedurą, łączenie dynamiczne może nie wskazywać na otaczający rekord aktywacji procedury
Jaka jest rzeczywista potrzeba wskaźnika do otaczającego rekordu aktywacji procedury. Wiele
kompilatorów w językach o strukturze blokowej tworzy takie wskaźniki, łączenie (linkowanie) statyczne. Rozważmy
poniższy Pascalowski kod:
Procedure Parent;
var i, j: integer;
procedure Child1;
var j :integer;
begin
for j := 0 to 2 do writeln(i);
end {Child1};
procedure Child2;
var i: integer;
begin
for i := 0 to 1 do Child1;
end {Child2};
[ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • tejsza.htw.pl
  •