Łączna liczba wyświetleń

czwartek, 25 lutego 2016

6. Kurs Asembler x86: Stałe symboliczne

Autor Nauka Programowania  |  w x86  17:08


Stała symboliczna tworzona jest poprzez powiązanie identyfikatora z liczbą lub tekstem. Taka stała nie zajmuje miejsca w pamięci, wystąpienia jej identyfikatora w programie zamieniane są na jej wartość przez tłumaczeniem programu na kod maszynowy. W związku z tym wartość takiej stałej nie może zostać zmieniona w trakcie działania programu.

Przypisanie


Przypisanie wiąże ze sobą nazwę i wyrażenie liczbowe za pomocą znaku =. Kiedy program jest tłumaczony na kod maszynowy, wszystkie wystąpienia danego symbolu zamieniane są na jego wartość.

Kod programu:

.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data

.code
main PROC
    LICZBA = 1000
    mov eax, liczba

    invoke ExitProcess, eax ;tutaj ustaw breakpoint
main ENDP
END main



Podczas tłumaczenia programu, dany kod:

mov eax, liczba

Zostanie przetłumaczony na:

mov eax, 1000



Wynik działania programu




Do czego może się to przydać? Takie działanie jest przydatne, gdy w kodzie programu używamy jednej wartości wiele razy. Gdy zajdzie potrzeba jej zmiany, nie musimy ręcznie zmieniać jej wystąpienia na każdej linijce, wystarczy zmienić jej wartość przypisaną do stałej.

Stała taka może ulegać redefinicji. Przechodząc przez, każdą instrukcję programu klawiszem F10 można zobaczyć jak wartość rejestru EAX ulega zmianie.

.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data

.code
main PROC
    mov eax, 0

    LICZBA = 10
    mov al, liczba

    LICZBA = 20
    mov al, liczba

    LICZBA = 30
    mov al, liczba

    invoke ExitProcess, eax
main ENDP
END main



Wskaźnik aktualnej lokalizacji $


Korzystając z tablic zazwyczaj chcielibyśmy znać jej rozmiar. Można go zadeklarować w sposób bezpośredni.

Tablica czterech bajtów będzie zajmowała 4 bajty w pamięci.

.data
    tablicaByte BYTE 10, 20, 30, 40
    tablicaByteRozmiar = 4



Ale, żeby nie liczyć rozmiaru tablicy, można użyć do tego celu wskaźnika aktualnej lokalizacji $.

Kod
Wynik
.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data
    tablica BYTE 10, 20, 30, 40
    rozmiar = ($ - tablica)

.code
main PROC
    mov eax, rozmiar

    invoke ExitProcess, eax
main ENDP
END main
EAX = 4



Nazwa tablica jest adresem pierwszego elementu tej tablicy. Pod stałą rozmiar znajduje się adres tego miejsca w kodzie – adres tablica.

Załóżmy, że adresacja wygląda w następujący sposób

Adres w bajtach
Zawartość pamięci
1000
10
1001
20
1002
30
1003
40
1004
???

Adres 1004 oznaczony jest jako ??? ponieważ, nie wiemy co się znajduje pod tym adresem, nasza tablica sięga tylko do adresu 1003. Natomiast podczas tłumaczenia programu asembler wie, gdzie skończyła się tablica i gdzie się zaczeła. Stała rozmiar znajduje się tuż po tablicy więc, gdyby była to zmienna otrzymałaby adres 1004 i asembler o tym wie, więc może podstawić ten adres pod znak $. Więc w tym wyrażniu:

rozmiar = ($ - tablica)

Podstawi następujące wartości

rozmiar = (1004 - 1000)

I w stałej rozmiar zapisze wartość 4 czyli ilość elementów w tablicy.

W ten sposób można liczyć każdy rozmiar, należy tylko wziąć pod uwagę jaki rozmiar ma pojedynczy element. Dane typu WORD, zajmują 16 bitów czyli 2 bajty, dlatego żeby uzyskać ilość elementów należy otrzymaną liczbę (czyli ilość bajtów) podzielić przez 2.

Kod
Wynik
.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data
    tablica WORD 1000h, 2000h, 3000h, 4000h
    rozmiar = ($ - tablica) / 2

.code
main PROC
    mov eax, rozmiar

    invoke ExitProcess, eax
main ENDP
END main
EAX = 4



Kolejny przykład, każdy znak zajmuje jeden bajt.

Kod
Wynik
.386
.model flat, stdcall
.stack 4096

include kernel32.inc
includelib kernel32.lib

.data
    zdanie BYTE "Ala ma kota"
           BYTE " bo siertka ma rysia"
    rozmiar = ($ - zdanie)

.code
main PROC
    mov eax, rozmiar

    invoke ExitProcess, eax
main ENDP
END main
EAX = 31



Dyrektywa EQU


Dyrektywa EQU wiąże pewną nazwę z jakimś wyrażeniem

nazwa EQU wyrazenie
nazwa EQU symbol
nazwa EQU <tekst>

W pierwszym przypadku wyrażenie musi być poprawnym wyrażeniem liczbowym. W drugim przypadku symbol to jeden z istniejących już symbol zdefiniowanych przez = lub przez EQU. W trzecim przypadku między <> może wystąpić dowolny tekst.

Przykłady

macierz1 EQU 10 * 10
macierz2 EQU <10 * 10>

.data
M1 WORD macierz1
M2 WORD macierz2



Macierz1 ma przypisaną od razu wartość 100 ponieważ 10 * 10 jest to wyrażenie. Podczas tworzenia zmiennej M1 pod symbol macierz1 zostanie podstawiona wartość 100. Macierz2 ma przypisany ciąg znaków 10 * 10. Podczas tworzenia zmiennej M1 pod symbol macierz2 zostanie podstawiony ciąg znaków 10 * 10, który w tym przypadku też jest wyrażeniem. Asembler zobaczy, że jest tam 10 * 10 i dopiero wstawi wartość tego wyrażenia czyli 100.

M1 WORD 100
M2 WORD 10 * 10



W przeciwieństwie do symboli zdefiniowanych przez = symbole zdefiniowane przez EQU nie mogą ulec redefinicji.

Dyrektywa TEXTEQU


Ta dyrektywa tworzy element zwany makrem tekstowy. Są różne formaty takiego makra. Pierwszy przypisuje tekst, drugi przypisuje zawartość wcześniej zdefiniowanego makra, a trzeci przypisuje wyrażenie liczbowe.

nazwa TEXTEQU <tekst>
nazwa TEXTEQU makrotekstowe
nazwa TEXTEQU %wyrazanie



Przykład użycia:

zapytanie TEXTEQU <"Czy chcesz kontynuowac (T/N)?",0>

.data
tekst1 BYTE zapytanie


1 komentarz:

Proudly Powered by Blogger.