72
LEKCIJE IZ SISTEMSKOG PROGRAMIRANJA Sakupio: Ribić Samir Četvrta verzija (septembar 2009) 1

Sva predavanja na etfu iz predmeta kojeg drzi ribic

  • Upload
    diinnoo

  • View
    58

  • Download
    2

Embed Size (px)

DESCRIPTION

ribicev predmet na etfu

Citation preview

Page 1: Sva predavanja na etfu iz predmeta kojeg drzi ribic

LEKCIJE IZSISTEMSKOG PROGRAMIRANJA

Sakupio: Ribić Samir

Četvrta verzija

(septembar 2009)

1

Page 2: Sva predavanja na etfu iz predmeta kojeg drzi ribic

1. Programerov pogled na organizaciju procesora 7

1.1. Centralna Procesorska Jedinica 7

1.2. Dizajn procesora prema skupu instrukcija 7

1.3. Memorija 7

1.4. Dizajn procesora prema memorijskom pristupu 81.4.1. Trodresne mašine 81.4.2. Dvoadresne mašine 81.4.3. Jednoadresne mašine (akumulatorske) 91.4.4. Jednoadresne mašine sa više registara 91.4.5. Nula adresne mašine (stekovne) 9

1.5. 80x86 familija mikroprocesora 10

2. Adresiranje podataka na sistemskom nivou 12

2.1. Registri 122.1.1. 8086 16-bitni registri 122.1.2. 80386 32-bitni registri 12

2.2. Dobijanje fizičke memorijske adrese na PC procesorima 132.2.1. Realni režim 132.2.2. 16-bit zaštićeni režim 132.2.3. 32-bitni zaštićeni režim 14

2.3. Instrukcijski operandi 14

2.4. Indirektno adresiranje 142.4.1. Napredno indirektno adresiranje 15

2.5. Predstavljanje cijelih brojeva 152.5.1. Znak i vrijednost 152.5.2. Prvi komplement 152.5.3. Drugi komplement 162.5.4. Smanjenje veličine podataka 172.5.5. Povećanje veličine podataka 17

2.6. Brojevi u pokretnom zarezu 172.6.1. Necijeli binarni brojevi 172.6.2. IEEE predstavljanje pokretnog zareza 19

2.7. Stek 19

3. Mašinski kod i njegovo generisanje 20

3.1. Mašinski jezik 20

3.2. Asemblerski jezik 20

3.3. Osnovne instrukcije 20

3.4. Direktive 213.4.1. Direktiva equ 213.4.2. Direktiva %define 213.4.3. Direktive podataka 22

3.5. Prvi program 233.5.1. Kompajlerske specifičnosti 253.5.2. Asembliranje programa 253.5.3. Kompajliranje C programa 263.5.4. Linkovanje objektnih programa 263.5.5. Razumijevanje asemblerskog listinga 263.5.6. Big i Little Endian predstavljanje 273.5.7. Uzorak za pisanje programa 27

3.6. Instrukcije za proširenje veličine podataka 28

3.7. Aritmetičke instrukcije 29

2

Page 3: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.7.1. Primjer program 303.7.2. Instrukcije proširene tačnosti 31

3.8. Kontrolne strukture 323.8.1. Poređenja 323.8.2. Instrukcije grananja 323.8.3. Instrukcije petlji 35

3.9. Operacije pomijeranja bitova 353.9.1. Logičko pomijeranje 353.9.2. Upotreba pomijeranja 363.9.3. Aritmetičko pomijeranje 363.9.4. Rotiranje bitova 363.9.5. Primjer upotrebe instrukcija za pomijeranje i rotiranje 373.9.6. Bulove bit operacije 373.9.7. AND operacija 373.9.8. OR operacija 373.9.9. XOR operacija 383.9.10. NOT operacija 383.9.11. TEST instrukcija 383.9.12. Upotreba bit operacija 39

3.10. Izbjegavanje uslovnih skokova 39

3.11. Instrukcije zamjene podataka 40

3.12. Potprogrami 403.12.1. Manipulacija stekom 403.12.2. Instrukcije CALL i RET 40

4. Generisanje koda radi implementiranja programskih jezika visokog nivoa 41

4.1. Prevođenje standardnih kontrolnih struktura 414.1.1. If naredba 414.1.2. While petlja 414.1.3. Do while petlja 41

4.2. Potprogrami 424.2.1. Konvencije poziva 424.2.2. Prosljeđivanje parametara na steku 424.2.3. Lokalne varijable na steku 454.2.4. Višemodulni programi 47

4.3. Povezivanje asemblera i C-a 484.3.1. Čuvanje registara 494.3.2. Oznake funkcija 494.3.3. Redoslijed prosljeđivanje parametara 494.3.4. Računanje adrese lokalnih varijabli 504.3.5. Povratne vrijednosti 504.3.6. Druge konvencije poziva 504.3.7. Poziv C funkcija iz asemblerskog programa 534.3.8. Ponovno pozivljivi i rekurzivni potprogrami 544.3.9. Rekurzivni potprogrami 544.3.10. Tipovi varijabli u C-u 55

4.4. Nizovi 554.4.1. Definisanje nizova 564.4.2. Definisanje nizova kao lokalne varijable na steku 564.4.3. Pristup elementima niza 564.4.4. Druge primjene instrukcije LEA 584.4.5. Višedimenzionalni nizovi 584.4.6. Dvodimenzionalni nizovi 584.4.7. Dimenzije iznad dva 594.4.8. Prosljeđivanje višedimenzionalnih nizova kao parametara u C 59

4.5. Instrukcije za rad s nizovima i stringovima 604.5.1. Čitanje i pisanje memorije 604.5.2. REP instrukcijski prefiks 614.5.3. String instrukcije za poređenje 62

3

Page 4: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.5.4. Instrukcijski prefiksi REPx 62

4.6. Pokretni zarez 634.6.1. Numerički koprocesor 634.6.2. Učitavanje i smještanje 644.6.3. Osnovne aritmetičke operacije 644.6.4. Razne instrukcije 67

4.7. Strukture 67

5. Ulaz i izlaz 69

5.1. Memorijski i U/I mapirani uređaji 69

5.2. Video 705.2.1. Video na niskom nivou 705.2.2. Video na nivou Windowsa 725.2.3. Video na nivou Linuxa 76

5.3. Tastatura 765.3.1. Tastatura na niskom nivou 765.3.2. Tastatura na nivou Windowsa 775.3.3. Tastatura na nivou Linuxa 77

5.4. Disk 775.4.1. Disk na niskom nivou 775.4.2. Disk na nivou Windowsa 785.4.3. Disk na nivou Linuxa 79

6. Prekidi 81

6.1. Šta je to prekid? 816.1.1. Hardverski interapti 816.1.2. Softverski interapti - trapovi 826.1.3. Izuzeci 82

6.2. Obrada prekida u realnom režimu 82

6.3. Obrada prekida u zaštićenom režimu 82

6.4. Kako izgleda tipična rutina za obradu prekida 83

7. Kompajleri 84

7.1. Prolazi 84

7.2. Glavne faze kompilacije 847.2.1. Predprocesiranje 847.2.2. Leksička analiza 847.2.3. Parsiranje, sintaksna analiza 857.2.4. Semantička analiza 857.2.5. Kompajlerska analiza 857.2.6. Optimizacija 857.2.7. Generisanje koda 85

7.3. LL i LR parsiranje 85

7.4. Sintaksni dijagrami i sintaksne procedure kroz primjer kompajlera 867.4.1. Jezici 867.4.2. Opis kompajlera 877.4.3. Osnovni principi 877.4.4. Sintaksni dijagrami i listing kompajlera 887.4.5. Ulaz, izlaz i dohvatanje simbola 927.4.6. Numeričke i alfanumeričke konstante 927.4.7. Varijable i nizovi 927.4.8. Izrazi 937.4.9. Kontrolne strukture 947.4.10. Funkcije 947.4.11. Uvodni i završni dio 947.4.12. Testni primjer i upotreba kompajlera 95

4

Page 5: Sva predavanja na etfu iz predmeta kojeg drzi ribic

8. Drugi razvojni alati 98

8.1. Linker 988.1.1. Linkerova pravila 988.1.2. Fiksiranja i relokacije 998.1.3. Biblioteke 100

8.2. Dinamičke biblioteke 1008.2.1. Dinamičke bibilioteke 1008.2.2. Upravljanje memorijom u DLL 1018.2.3. Eksplicitno i implicitno povezivanje DLL 1018.2.4. Primjer pravljenja DLL i poziva sa implicitnom i eksplicitnom varijantom. 102

8.3. Make 1038.3.1. Makefile struktura 1038.3.2. Primjer makefile 103

8.4. Asembleri 1048.4.1. Dvoprolazni asembler 1048.4.2. Jednoprolazni asembler 105

8.5. Interpreteri 105

8.6. Komandne linije, Integrirana okruženja i vizualna razvojna okruženja 107

9. Izvršno okruženje 110

9.1. Windowsov izvršni format datoteke 1109.1.1. MS-DOS zaglavlje i osnovni program u realnom režimu 1109.1.2. PE signatura, PE zaglavlje i opcionalno zaglavlje 1109.1.3. Standardne sekcije 1119.1.4. Sekcija s izvršnim programom, .text 1119.1.5. Sekcije podataka, .bss, .rdata, .data 1119.1.6. Resursna sekcija, .rsrc 1119.1.7. Sekcija izvezenih podataka .edata i uvezenih podataka .idata 1119.1.8. Sekcija debagerskih informacija .debug 1119.1.9. Linux ELF izvršni format 111

9.2. Loader (Punilac) 112

9.3. Windowsove sistemske funkcije 1129.3.1. Windows API 1129.3.2. Osnovne usluge 1129.3.3. Grafičke funkcije (GDI) 1129.3.4. Korisnički interfejs 1139.3.5. Mrežni servisi 1139.3.6. Web API funkcije 1139.3.7. Multimedijalni APIji 1139.3.8. API za interakciju između programa 1139.3.9. Prirodni API 1139.3.10. Omotači oko API-ja 1139.3.11. Pozivanje API funkcija 1149.3.12. Lista funkcija 114

9.4. Linuxove sistemske funkcije 125

9.5. Virtualne mašine 1259.5.1. Virtualne mašine koje omogućavaju istovremeno izvršavanje više operativnih sistema 1269.5.2. Aplikacijska virtualna mašina 1269.5.3. Virtualno okruženje 1269.5.4. Spajanje fizičkih mašina u virtualnu mašinu 127

10. Uvod u konkurentno programiranje 128

10.1. Zašto su potrebne niti 128

10.2. Niti (thread) 128

10.3. Kreiranje niti pod Linuxom 12910.3.1. Kreiranje niti 129

5

Page 6: Sva predavanja na etfu iz predmeta kojeg drzi ribic

10.3.2. Čekanje na završetak niti 129

10.4. Niti pod Windowsom 130

10.5. Problem konkurentnosti 13110.5.1. Uzajamno isključivanje 13110.5.2. Bibliotečne funkcije i niti 132

11. Optimizacija koda i perfomanse 133

11.1. Kada optimizovati, kada ne 13311.1.1. Kasna optimizacija 13311.1.2. Rana optimizacija 13411.1.3. Optimizacija u pravom vremenu 134

11.2. Kako se traži spori kod u programima? 13411.2.1. Profajleri 135

11.3. Da li je optimizacija neophodna? 135

11.4. Tri tipa optimizacije 13611.4.1. Bolji algoritam 13611.4.2. Bolja implementacija 13611.4.3. Brojanje ciklusa 136

11.5. Benchmark programi 137

11.6. Optimizacija izlaza kompajlera 137

11.7. Proširenje kompajlera Fildzan32 optimizatorom 138

12. Prilog: Instrukcijski set Pentiuma II 141

12.1.1. Operacioni kodovi, opis 14112.1.2. Primjeri kodiranja kompletnih instrukcija 143

6

Page 7: Sva predavanja na etfu iz predmeta kojeg drzi ribic

1. Programerov pogled na organizaciju procesora

1.1.Centralna Procesorska Jedinica

Centralna procesorska jedinica (CPU) je fizički uređaj koji izvršava instrukcije. Instrukcije koje CPU obavlja su uglavnom veoma jednostavne. Instrukcije mogu zahtijevati podatke s kojim rade da se nalaze u specijalnim lokacijama unutar samog CPU, koje se zovu registri. CPU može pristupati podacima u registrima daleko brže nego u memoriji. Ipak, broj registara u CPU je ograničen, pa programer mora biti pažljiv tako da čuva u registrima samo trenutno korištene podatke.

Instrukcije koje CPU izvršava predstavljaju njegov mašinski jezik. Mašinski programi imaju mnogo jednostavniju strukturu nego programi u jeziku visokog nivoa. Mašinski programi su kodirani kao sirovi brojevi, a ne u razumljivom tekstualnom formatu. CPU mora biti u stanju da dekodira namjenu instrukcije veoma brzo da bi se radilo efikasno. Mašinski jezik je stoga dizajniran s tim ciljem u vidu, a ne da bude lako razumljiv ljudima. Programi pisani u drugim jezicima se moraju konvertirati u prirodni jezik procesora da bi se izvršavali na računaru. Kompajler je program koji prevodi program napisan u nekom programskom jeziku u mašinski jezik odgovarajuće računarske arhitekture. Generalno, svaki tip procesora ima svoj vlastiti mašinski jezik. To je jedan od razloga zašto programi za Mekintoš ne mogu da rade na IBM-PC računarima.

Računar koristiti sat da uskladi izvršavanje date naredbe. Sat otkucava na fiksnoj frekvenciji ( poznatoj kao brzina sata). Ako ste kupili 2 GHz kompjuter, 2 GHz je frekvencija ovog sata. Sat ne prati minute i sekunde On prosto otkucava konstantnim ritmom. Elektronika procesora koristi otkucaje da izvršava svoje operacije korektno, slično kao što otkucaji metronoma omogućavaju sviranje muzike korektnim ritmom. Broj otkucaja (ili kako se obično zovu ciklusa) koju instrukcija zahtijeva zavisi od generacije procesora i modela. Broj ciklusa koliko traje instrukcija zavisi prije svega od samih instrukcija, ali i od drugih faktora.

1.2.Dizajn procesora prema skupu instrukcija

Postoje dva pristupa dizajnu računara : - RISC: Računar s reduciranim skupom instrukcija. Samostalne instrukcije obavljaju jednu, dobro

definiranu operaciju. RISC pristup pretpostavlja da će se većina programiranja obavljati u jezicima visokog nivoa. Ovaj pristup povećava broj instrukcija koje je potrebno upotrebiti u programu, ali pojednostavljuje strukturu procesora.

- CISC: Računar s kompleksnim skupom instrukcija. Jedna instrukcija obavlja više operacija. Jednostavnije je pisati programe u mašinskom i asemblerskom jeziku, ali se kompleksnost procesora povećava.

1.3.Memorija

Osnovna jedinica memorije je bajt. Računar sa 32 megabajta memorije može sadržavati približno 32 miliona bajtova podataka. Svaki bajt u memoriji je označen jedinstvenim brojem koji se zove adresa ( slika 1). Mikroprocesor čita kodove instrukcija i podatke iz memorije pristupajući njihovim adresama.

Lokacija 0 1 2 3 4 5 6 7

Vrijednost 2E 3A 4C 88 72 26 83 9F

Sl. 1 Memorijske Adrese

7

Page 8: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Često se memorija koristi u većim komadima od pojedinačnih bajtova. Na PC arhitekturi, data su imena i većim sekcijama memorije. Memorija se mjeri u kilobajtima ( 1024 bajta), megabajtima (1 048 576 bajtova) i gigabajtima ( 1073741824 bajta). Četvorobitni broj se zove nibl. Stoga, svaka heksadekadna cifra predstavlja nibl. Dva nibla čine bajt, pa se bajt predstavlja dvocifrenim heksadekadnim brojem. Vrijednost bajta varira između 0 i 11111111 u binarnom sistemu, 0 i FF u heksadekadnom te 0 to 255 u dekadnom.

Riječ (word) 2 bajtaDupla riječ 4 bajtaČetverostruka riječ 8 bajtovaparagraf 16 bajtova

Sl. 2 Jedinice memorije

Svi podaci u memoriji su numerički. Znakovi se smještaju korištenjem kodova koji mapiraju brojeve u znakove. Jedan od najčešćih kodova znakova je poznat pod imenom ASCII (American Standard Code for Information Interchange). Postoji i noviji, veći način kodiranja znakova koji ponekad zamjenjuje ASCII pod imenom Unicode. Glavna razlika između ova dva načina je što ASCII koristi jedan bajt za kodiranje znaka, a Unicode koristi dva bajta (ili riječ) po znaku. Na primjer, ASCII mapira bajt 4116 (6510) u znak A; Unicode mapira riječ 004116 u isto slovo. Pošto ASCII koristi bajt, ograničen je na samo 256 različitih znakova . Unicode proširuje ASCII vrijednosti na 16 bitne riječi i tako dopušta predstavljanje više znakova. To je važno za predstavljanje znakova svih jezika svijeta.

1.4.Dizajn procesora prema memorijskom pristupuSvaka naredba treba da obavi neku operaciju. Kompleksne operacije se realizuju kao kombinacija prostih operacija. Na primjer iskaz pridruživanja se sastoji od prostih operacija sabiranja, množenja, ostatka pri dijeljenju, smještanja u memoriju i čitanja iz memorije :

a = (b * c ) % (d + f );

Ove proste operacije se mogu realizovati na različite načine. Na primjer, sabiranje dva broja zahtijeva dva broja koja želimo sabrati i treba da vrati kao rezultat treći broj, ali to ne znači da se sva tri broja moraju nalaziti na memorijskim lokacijama. Prema broju memorijskih operanada , procesori se dijele na nula adresne , jednoadresne , dvoadresne i troadresne mašine.

1.4.1.Trodresne mašineSvi tri operanda su eksplicitno navedeni u većini instrukcija. * ADD dest, src1, src2: M[dest] = M[src1] + M[src2] * MULT dest, src1, src2: M[dest] = M[src1] * M[src2]

Mada ovaj pristup izgleda vrlo moćan , ima svojih loših strana. Instrukcije zahtijevaju mnogo pristupa RAM memoriji, što je znatno sporije nego instrukcije unutar procesora. Najpopularnija 3-adresna mašina je bio računar VAX 11. Primjer troadresne instrukcije na ovom procesoru (ne mikroprocesoru) jeaddl3 (R2),(R3),(R4) koja sabira vrijednosti na lokacijama koje pokazuju registri R2 i R3 i upisuje rezultat na adresu na koju pokazuje registar R4.

1.4.2.Dvoadresne mašineU 2-adresnoj mašini, instrukcije imaju samo dvije eksplicitne adrese. Jedna od njih ima dvije uloge: adresa izvornog operanda i adresa odredišta.

* ADD dest, src: M[dest] = M[dest] + M[src] * MULT dest, src: M[dest] = M[dest] * M[src] * MOVE dest, src: M[dest] = M[src]

8

Page 9: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Najpopularnija 2-adresna mašina je mikroprocesor Motorola 68000. Primjer dvoadresne instrukcije za ovu arhitekturu je sub.l (A1),(A2) , koja oduzima vrijednost na adresi na koju pokazuje registar A1 od vrijednosti na adresi koju pokazuje A2 i rezultat smješta na adresu na koju pokazuje A2.

1.4.3.Jednoadresne mašine (akumulatorske)U jednoadresnoj mašini ili akumulatorskoj mašini, akumulator ima ulogu odredišnog i jednog od izvornih operanada. Potrebno je navesti samo adresu drugog izvornog operanda. Specifična je jedino STORE instrukcija, u kojoj je akumulator izvorni operand, a odredište je na memorijskoj lokaciji koja se eksplicitno navodi. Akumulator je registar u akumulatorskoj mašini. Akumulator se nalazi unutar CPU.

* ADD addr: accum = accum + M[addr] * MULT addr: accum = accum * M[addr] * LOAD addr: accum = M[addr] * STORE addr: M[addr] = accum

Evo kako se koriste gornje instrukcije za izračunavanje naredbe a = a * b + c * d * e. Pretpostavimo da se varijable a, b, c, d, e nalaze na memorijskim lokacijama 100, 104, 108, 112 i 116, u navedenom redu.

LOAD 100 ; acc = aMULT 104 ; acc *= bSTORE 100 ; a = accLOAD 108 ; acc = cMULT 112 ; acc *= dMULT 116 ; acc *= eADD 100 ; acc += aSTORE 100 ; a = acc

Najpopularniji procesor iz ove kategorije je Mos technology 6502, koji je ugrađivan u računare Apple II i Commodore 64. Primjer instrukcije na ovom mikroprocesoru je LDA 1000 koja prenosi vrijednost s lokacije 1000 u akumulator.

1.4.4.Jednoadresne mašine sa više registara

Mnoge moderne mašine (npr. CPU u svakom PC računaru) prihvatile su ideju jedne adrese, ali umjesto jednog akumulatora koriste više različitih internih registara. To ubrzava neke operacije. Na primjer, za izračun prethodnog primjera na 8086 imat ćemo

MOV AX,[100]MUL WORD [104]MOV BX,AXMOV AX,[108]MUL WORD [112]MUL WORD [116]ADD AX,BXMOV [100],AX

1.4.5.Nula adresne mašine (stekovne)Moguć je i pristup da se operandi nalaze na internom mjestu u memoriji ili procesoru. Jedan takav pristup je preko steka (o njemu više u posebnom poglavlju). Kod 0 adresne mašine operacije uzimaju vrijednosti sa steka i smještaju rezultate takođe na stek. Za prijenos podataka između memorijskih ćelija i stek koriste se

9

Page 10: Sva predavanja na etfu iz predmeta kojeg drzi ribic

specijalne operacije PUSH i POP. Mali dio steka je unutar procesora, ostatak je u memoriji. Ovakav koncept ima matematički koprocesor 8087, do procesora 486 pravljen kao poseban čip. Broj bajtova na steku (dubina steka) utiču na vrijeme izvršenja programa. Evo nekih instrukcija hipotetičke 0 adresne mašine.

* ADD: push ( pop + pop ) * MULT: push ( pop + pop ) * PUSH addr: push ( M[addr] ) * POP addr: M[addr] = pop

Pokazaće se kako se mogu koristiti gornje instrukcije da se izračuna naredba a = a * b + c * d * e. Pretpostavlja se da se varijable a, b, c, d, e nalaze na memorijskim lokacijama 100, 104, 108, 112, i 116, respektivno.

PUSH 100 ; aPUSH 104 ; a bMULT ; (a * b)PUSH 108 ; (a * b) cPUSH 112 ; (a * b) c dMULT ; (a * b) (c * d)PUSH 116 ; (a * b) (c * d) eMULT ; (a * b) (c * d * e)ADD ; (a * b + c * d * e)POP 100

1.5.80x86 familija mikroprocesora

Svaki IBM PC kompatibilni računar koristi procesor iz familije Intel 80x86 (ili srodan procesor drugog proizvođača). Procesori u ovoj familiji imaju neke zajedničke karakteristike, uključujući osnovni mašinski jezik. Ipak, novije verzije procesora imaju znatno veće mogućnosti.

8088,8086: Ovi procesori sa programerske tačke gledišta su identični. To su bili procesori korišteni u najranijim PC računarima. Oni imaju sljedeće 16 bitne registre: AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAGS. Podržavaju samo do jednog megabajta memorije i rade samo u realnom režimu rada. U tom režimu program može pristupiti bilo kojoj memorijskoj adresi, pa čak i memoriji dodijeljenoj drugim programima. To čini ispravljanje grešaka i sigurnost veoma otežanim. Također, programska memorija se mora dijeliti u segmente koji ne mogu biti veći od 64K.

80286: Ovaj procesor je korišten u AT klasi PC računara. On dodaje neke nove instrukcije osnovnom mašinskoj jeziku procesora 8088/86. Ipak, njegova glavna nova osobina je 16-bitni zaštićeni režim. U ovom režimu on može pristupiti do 16 megabajta i zaštititi programe od pristupa tuđoj memoriji. Ipak, programi su i dalje podijeljeni u segmente koji ne mogu biti veći od 64 K.

80386: Ovaj CPU je znatno poboljšan u odnosu na 80286. Prvo, prošireni su mnogi registri na veličinu 32 bita (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP) i dodana 2 nova 16 bitna registra FS i GS. Također uvodi novi 32-bitni zaštićeni režim. U ovom režimu, može se pristupiti memoriji do 4 gigabajta. Programi su i dalje podijeljeni u segmente, ali sada je svaki segment velik do 4 gigabajta.

80486/Pentium/Pentium Pro: Ovi procesori uključuju instrukcije za rad s pokretnim zarezom i uglavnom ubrzavaju izvršavanje instrukcija.

Pentium MMX/ Pentium II/Celeron: Ovi procesori dodaju MMX (MultiMedia eXtensions) instrukcije Pentiumu. Te instrukcije omogućavaju istovremene cjelobrojne operacije što ubrzava grafičke primjene..

Pentium III/Pentium IV su principijelno Pentium II sa SSE instrukcijama. Te instrukcije omogućavaju istovremene operacije u pokretnom zarezu. Neke verzije Pentium IV imaju 64 bitne registre.

Pentium Dual Core/Core 2/Core i7 uključuju 64 bitne instrukcije, 1,2,4, ili 8 procesora u istom integrisanom kolu, SSE3 instrukcije.

10

Page 11: Sva predavanja na etfu iz predmeta kojeg drzi ribic

2. Adresiranje podataka na sistemskom nivou

2.1.Registri

Sl. 3 Registar AX

2.1.1.8086 16-bitni registri

Originalni 8086 procesor je obezbjeđivao četiri 16-bitna registra opšte namjene: AX, BX, CX i DX. Svaki od ovih registara se može rastaviti u dva osmobitna registra. Na primjer, registar AX se može rastaviti na registre AH i AL, kako se vidi na Sl. 3 . Registar AH sadrži gornjih (ili viših) 8 bita registra AX a registar AL sadrži nižih osam bita registra AX. Često se AH i AL koriste kao međusobno nezavisni registri, ali je važno napomenuti da oni nisu nezavisni od registra AX. Promjena vrijednosti registra AX će promijeniti i registre AH i AL i obrnuto. Slično, BX se dijeli na BH i BL, CX na CH i CL, a DX na DH i DL. Registri opšte namjene se koriste u instrukcijama za premještanje podataka i aritmetičkim instrukcijama.

Postoje dva 16-bitna indeksna registra: SI i DI. Oni se često koriste kao pokazivači, ali se takođe koriste kao registri opšte namjene. Ipak, oni se ne mogu rastaviti u osmobitne registre.

16-bitni registri BP i SP se koriste da pokazuju na podatke na mašinskom steku i zovu se Base Pointer i Stack Pointer. O njima će biti kasnije riječi.

16-bitni registri CS, DS, SS i ES su segmentni registri. Oni određuju koji će se dio memorije koristiti za različite dijelove programa. CS predstavlja Code Segment, DS predstavlja Data Segment, SS predstavlja Stack Segment, a ES predstavlja Extra Segment. ES se koristi kao privremeni segmentni registar.

Instruction Pointer (IP) registar se koristi zajedno sa CS registrom radi praćenja adrese naredne instrukcije koju će izvršiti CPU. U normalnoj situaciji, kako se izvršava instrukcija, IP se uvećava da pokazuje na narednu instrukciju u memoriji.

Registar FLAGS čuva važne informacije o rezultatu prethodne instrukcije. Ovi rezultati se smještaju kao

individualni bitovi u registru. Na primjer, Z bit je 1 ako je rezultat prethodne instrukcije bio 0, a Z bit je 0 ako rezultat prethodne instrukcije nije bio 0. Ne mijenjaju sve instrukcije bite u FLAGS registru.

2.1.2.80386 32-bitni registriNa 80386 i kasnijim procesorima su prošireni registri. Na primjer, 16 bitni AX je proširen na 32 bita.

Radi kompatibilnosti sa starim procesorima, AX se odnosi na 16-bitni registar dok se EAX koristi da pokazuje prošireni, 32-bitni registar. AX je nižih 16 bita registra EAX, baš kao što je AL nižih 8 bita registra AX (kao i EAX). Nema direktnog načina za pristup do viših 16 bita EAX registra. Ostali prošireni registri su EBX, ECX, EDX, ESI i EDI.

Prošireni su još neki registri. BP je postao EBP; SP je postao ESP; FLAGS je postao EFLAGS a IP je postao EIP. Ipak, za razliku od registara opšte namjene, u 32-bitnom zaštićenom režimu (bit će o njemu kasnije riječi) koristi se samo proširena verzija ovih registara.

Segmentni registri su i dalje 16-bitni na 80386. Uvedena su dva nova segmentna registra: FS i GS, bez posebnog značenja njihovih imena. Oni su pomoćni segmentni registri poput ES.

Termin “riječ” se ponekad definiše kao veličina registara podataka procesora. Za 80x86 familiju ovaj termin je sada malo zbunjujući. Na Sl. 2, može se vidjeti da je “riječ” definisana da bude 2 bajta (ili 16 bita). To je značenje dato kada se 8086 pojavio. Kada se pojavio 80386, odlučeno je da se definicija pojma “riječ” ne mijenja, iako se promijenila veličina registra.

11

AH AL

AX

Page 12: Sva predavanja na etfu iz predmeta kojeg drzi ribic

2.2.Dobijanje fizičke memorijske adrese na PC procesorima

2.2.1.Realni režim

U realnom režimu rada (kompatibilnom sa 8086 i aktivnom pri startu računara) memorija je ograničena na samo jedan megabajt. Ispravne adrese u opsegu (heksadekadno) između 00000 i FFFFF. Ove adrese zahtijevaju 20-bitne brojeve. Jasno, 20-bitni broj se ne može smjestiti u 16-bitne registre procesora 8086. Intel je riješio taj problem korištenjem dvije 16-bitne vrijednosti radi određivanja adrese. Prva 16 bitna vrijednost se zove selektor ili segmentna vrijednost. Vrijednosti selektora se moraju čuvati u segmentnim registrima. Druga šesnaestobitna vrijednost se zove pozicija (offset). Fizička adresa koja se dobije 32 bitnim parom selektor-pozicija se računa formulom

1610* selektor + pozicija

Množenje sa 1610 u heksadekadnom sistemu je lako, samo se dodaje 0 na kraj broja. Na primjer, fizička adresa navedena kao 047C16:004816 iznosi:

047C0+0048--------04808

U suštini, vrijednost selektora je broj paragrafa (Sl. 2). Segmentno adresiranje u realnom režimu ima tri nedostatka:

• Jedan selektor može pristupiti samo 64K memorije (gornji limit 16-bitne adrese). Šta ako program ima više od 64K koda? Pojedinačna vrijednost u CS se ne može koristiti tokom cijelog programa. Program se mora rasjeći u sekcije (zvane segmenti) koji su kraći od 64 kilobajta. Kada se izvršenje programa premješta s jednog segmenta na drugi, vrijednost CS registra se mora promijeniti. Slični problemi se dešavaju s velikom količinom podataka i DS registrom. To može biti vrlo neugodno!

• Svaki bajt u memoriji nema jedinstvenu segmentnu adresu. Fizičkoj adresi 04808 moće se pristupiti kao 047C:0048, 047D:0038, 047E:0028 ili 047B:0058. To može otežati poređenje segmentiranih adresa.

• Nema zaštite između segmenata, pa je moguće upisivanje podataka u područje predviđeno za drugi program.

2.2.2.16-bit zaštićeni režim

U 80286 16-bitnom zaštićenom režimu rada, vrijednosti selektora se interpretiraju potpuno drugačije nego u realnom režimu. U realnom režimu, vrijednost selektora je broj paragrafa fizičke memorije. Kod zaštićenog režima, vrijednost selektora je redni broj elementa u deskriptorskoj tabeli, kombinovan s još tri bita (od kojih jedan određuje koji od dva moguća deskriptorska registra pokazuje na tabelu, a dva bita određuju s kojim pravima se želi pristupiti memorijskom segmentu). Za oba režima, program se dijeli u segmente. U realnom režimu ovi segmenti su na fiksnoj lokaciji u fizičkoj memoriji i vrijednost selektora određuje redni broj paragrafa odakle počinje segment. U zaštićenom režimu, segmenti nisu na fiksnoj poziciji u fizičkoj memoriji. Čak, ne moraju uopšte biti u RAM memoriji.

Zaštićeni režim koristi tehniku koja se zove virtualna memorija. Osnovna ideja virtualne memorije da se u RAM-u čuvaju samo podaci i program koji se trenutno koristi. Ostali podaci i programski kod se privremeno drže na disku dok ne budu opet potrebni. U 16-bitnom zaštićenom režimu, segmenti se premještaju između memorije i diska po potrebi. Kada se segment vrati sa diska u memoriju, vrlo je vjerovatno da će biti smješten u drugi dio memorije u odnosu na onaj gdje je bio prije nego što je prebačen na disk. Sve se ovo radi transparentno, u operativnom sistemu. Sam program ne mora biti posebno pisan da radi s virtualnom memorijom.

12

Page 13: Sva predavanja na etfu iz predmeta kojeg drzi ribic

U zaštićenom režimu, svakom segmentu je pridružen element deskriptorske tabele. Taj element ima sve informacije potrebne sistemu da zna o segmentu. Informacije uključuju: da li je trenutno u memoriji, ako jeste – gdje je, koja su prava nad datim segmentom (npr samo za čitanje). Redni broj elementa segmenta je vrijednost selektora smještena u segmentnim registrima.

Mana 16-bitnog zaštićenog režima je da su memorijske pozicije i dalje 16 bitne vrijednosti. Kao posljedica ovoga, veličina segmenata je limitirana na najviše 64 kilobajta. To čini problematičnom upotrebu velikih nizova.

2.2.3.32-bitni zaštićeni režim

80386 uveo je 32-bit zaštićeni režim. Postoje dvije glavne razlike između 32-bitnog režima za 386 i 16 bitnog režima za 286:

1. Memorijske pozicije su povećane na 32 bita. To omogućuje postavljanje pozicije na vrijednost preko 4 milijarde. Tako, segmenti mogu imati veličine do 4 gigabajta.

2. Segmenti se mogu dijeliti na manje jedinice od 4 kilobajta, koje se zovu stranice. Sistem virtualne memorije sada radi s stranicama umjesto segmenata. To znači da u memoriji možemo držati i samo dijelove segmenata. U 16-bitnom režimu procesora 80286 , ili je cijeli segment u memoriji ili nije. To nije praktično sa velikim segmentima kakve dopušta 32-bitni režim.

U Windows 3.1, standardni režim je u stvari 286 16-bitni zaštićeni režim, a prošireni režim je 32-bitni zaštićeni režim. Windows 9X, Windows NT/2000/XP, OS/2 i Linux svi rade u 32-bitnom (najnovije verzije i 64 bitnom) zaštićenom režimu.

13

Page 14: Sva predavanja na etfu iz predmeta kojeg drzi ribic

2.3.Instrukcijski operandi

Instrukcije mašinskog jezika se razlikuju po broju i tipu operanada, ali svaka instrukcija sama za sebe ima fiksan broj operanada (0 do 3). Operandi mogu biti sljedećeg tipa:

registar: Operandi se nalaze direktno u procesorskim registrima

memorija: Operand se nalazi na memorijskoj lokaciji. Adresa memorijske lokacije se može direktno nalaziti u instrukciji, ili se može računati korištenjem vrijednosti registara. Adrese su uvijek pozicije od početka segmenta.

neposredni: To su fiksne vrijednosti koje se nalaze u samoj instrukciji. (tako da su u kodnom, a ne u segmentu podataka)

implicitni: Operandi nisu eksplicitno navedeni. Npr, inkrementiranje uvećava sadržaj registra ili memorijske lokacije za 1. Ta vrijednost se podrazumijeva i ne navodi se.

2.4.Indirektno adresiranje

Indirektno adresiranje omogućuje registrima da se ponašaju kao pokazivačke varijable. Da se označi da se registar koristi indirektno kao pokazivač, uokviren je uglastim zagradama ([ ]). Na primjer:mov ax, [Data] ; normalno direktno memorijsko adresiranje riječimov ebx, Data ; ebx = & Datamov ax, [ebx] ; ax = *ebx

Pošto AX sadrži 16bitnu riječ, linija 3 čita riječ koja počinje na adresi koja je navedena u registru EBX. Ako se AX zamijeni sa AL, bio bi pročitan samo jedan bajt. Važno je shvatiti da registri nemaju tipove, kao što ih imaju varijable u jeziku C. Ono što bi EBX pokazivao je potpuno određeno time koja instrukcija se koristi. Zapravo, instrukcijom je određena i činjenica da je EBX pokazivač. Ako se EBX nepravilno koristi, često ne bi bilo prijavljene asemblerske greške ali program ne bi ispravno radio. To je jedan od glavnih razloga zašto je asemblersko programiranje mnogo ranjivije na greške nego programiranje u jeziku visokog nivoa.

Svi 32-bitni opšte namjenski registri (EAX, EBX, ECX, EDX) i indeksni registri (ESI, EDI), pa uz oprez i registri za pristup steku (ESP, EBP) se mogu koristiti za indirektno adresiranje. Nijedan osmobitni registar se ne može koristiti za indirektno adresiranje a od 16 bitnih mogu samo BX, SI, DI i BP. Prilikom korištenja indirektnog adresiranja 80x86 procesor pristupa različitim segmentima zavisno od toga koji su se registri koristili tokom indirektnog adresiranja. ESP i EBP koriste segment steka, dok EAX, EBX, ECX and EDX koriste segment podataka. Ipak, ovo je obično nevažno za većinu programa u zaštićenom režimu pošto su u njemu segment podataka i segment steka jednaki.

2.4.1.Napredno indirektno adresiranje

Indirektno adresiranje se često koristi za realizaciju nizova, pa je uvedena generalna forma za indirektni pristup memoriji.

[ bazni registar+ faktor *indeks reg+ konstanta ]

gdje su:

bazni registar jedan od EAX, EBX, ECX, EDX, EBP, ESP, ESI ili EDI. faktor je jedan od 1, 2, 4 ili 8. (Ako je 1, faktor se ne navodi)

14

Page 15: Sva predavanja na etfu iz predmeta kojeg drzi ribic

indeks reg je jedan od registara EAX, EBX, ECX, EDX, EBP, ESI, EDI ili se može izostaviti. ( ESP nije na listi.)konstanta je 32-bitna konstanta. Konstanta može biti i labela

2.5. Predstavljanje cijelih brojeva

Cijeli brojevi se mogu posmatrati kao predznačeni i nepredznačeni. Nepredznačeni brojevi (pozitivni i nula) se predstavljaju na dosta jednostavan način. Broj 200 dekadno se može u binarnom sistemu u jedan bajt predstaviti kao 11001000 (ili C8 heksadekadno).

Predznačeni cijeli brojevi (mogu biti pozitivni ili negativni) se predstavljaju na znatno složeniji način. Uzeće se za primjer broj -56. +56 kao bajt se može predstaviti kao 00111000. Na papiru -56 se može predstaviti kao -111000, ali kako ovo predstaviti kao bajt u računarskoj memoriji? Gdje smjestiti znak minus?

Postoje tri glavne tehnike za predstavljanje predznačenih cijelih brojeva u memoriji, Sve one koriste najznačajniji bit cijelog broja kao bit predznaka. Taj bit ima vrijednost 0 za pozitivne i 1 za negativne brojeve.

2.5.1.Znak i vrijednost

Prvi metod je najjednostavniji i zove se znak i vrijednost. Predstavlja cijeli broj iz dva dijela. Prvi dio je bit predznaka, a drugi je apsolutna vrijednost cijelog broja. Tako, broj 56 se može predstaviti kao 00111000 (predznak podebljan) a -56 kao 10111000. Najveća vrijednost je stoga 01111111 ili +127 a najmanja je 11111111 ili -127. Negiranje broja se vrši izmjenom najvišeg bita. Metod je jasan, ali ima nedostatke. Kao prvo, postoje dvije vrijednosti za 0, +0 (00000000) i -0 (10000000). Pošto nula nije ni pozitivna ni negativna, obije reprezentacije se moraju jednako ponašati. To komplikuje aritmetičko logičku jedinicu procesora. Isto tako komplikuje se i opšta aritmetike. Ako se treba sabrati 10 za –56, to se mora realizovati pomoću oduzimanja broja 10 od 56.

2.5.2.Prvi komplement

Drugi metod je poznat pod imenom reprezentacija u prvom komplementu. Prvi komplement se dobije inverzijom svakog bita u broju. (Ili: Nova vrijednost jednako 1 – stara vrijednost.) Na primjer, prvi komplement broja 00111000 (+56) je 11000111. U ovoj notaciji, račun prvog komplementa je ekvivalentan negiranju. Tako, 11000111 je predstavljanje broja -56. I bit predznaka automatski je promijenjen prvim komplementom. Kako i kod prvog metoda, imaju dvije predstave nule: 00000000 (+0) i 11111111 (-0). Aritmetika s prvim komplementom je komplikovana. Postoji praktični trik za traženje prvog komplementa heksadekadnog broja bez konverzije u binarno. Trik je oduzeti heksadekadnu cifru od F (15 dekadno). Trik se zasniva na činjenici da je broj bita djeljiv sa 4. Na primjer, +56 se predstavlja kao 38 heksadekadno. Pri traženju prvog komplementa, oduzme se svaka cifra od F da se dobije C7 heksadekadno. To se slaže sa prethodnim rezultatom.

2.5.3.Drugi komplement

Prva dva metoda su korištena na ranim kompjuterima. Moderni kompjuteri koriste treći metod, koji se zove predstavljanje u formi drugog komplementa. Drugi komplement se nalazi u sljedeća dva koraka.

1.Nađi prvi komplement broja

2.Dodaj 1 rezultatu iz prvog koraka.

Evo primjera sa brojem 00111000 (56). Na početku se izračuna prvi komplement: 11000111. Zatim se doda 1:

11000111 + 111001000

15

Page 16: Sva predavanja na etfu iz predmeta kojeg drzi ribic

U notaciji drugog komplementa, računanje drugog komplementa je ekvivalentno negaciji broja. Tako, 11001000 je reprezentacija broja –56 u drugom komplementu. Dvije negacije trebaju reproducirati originalni broj. Zanimljivo je da drugi komplement zadovoljava ovaj uslov. Ako se uzme drugi komplement broja 11001000 dodavanjem jedinice prvom komplementu dobije se:

00110111 + 100111000

Prilikom obavljanja sabiranja u notaciji drugog komplementa sabiranje krajnjih lijevih bitova može izazvati prijenos. Taj prijenos se ne koristi. Razlog tome je što su podaci u računaru fiksne veličine (broja bita). Sabiranje i oduzimanje (ne i množenje i dijeljenje) dva bajta uvijek rezultuje bajtom, a sabiranje dvije šesnaestbitne riječi šesnaestbitnom riječju itd. To svojstvo je važno za notaciju u drugom komplementu. Na primjer, neka je nula broj u drugom komplementu (00000000). Izračun njenog drugog komplementa iznosi:11111111+ 100000000 C=1

gdje c predstavlja prijenos. (Kasnije će biti prikazano kako detektovati ovaj prijenos, ali on se ne smješta u rezultat.) Tako, u notaciji drugog komplementa postoji samo jedna nula. To čini aritmetiku drugog komplementa jednostavnijom od prethodnih metoda..

Broj Heksadekadno 0 001 01127 7F-128 80-127 81-2 FE-1 FF

Sl. 4: Predstavljanje u drugom komplementu

Koristeći notaciju drugog komplementa predznačeni broj može predstaviti brojeve između -128 to +127. Sl. 4 pokazuje neke vrijednosti. Ako se koriste 16 bitni brojevi, mogu se predstaviti brojevi između -32768 i +32767. Broj +32767 je predstavljen kao 7FFF, -32768 kao 8000, -128 kao FF80 a -1 kao FFFF. 32-bitni brojevi u drugom komplementu se kreću između –2147483648 i +2147483647.

Procesor ne zna šta pojedini bajt (riječ ili dupla riječ) treba da predstavlja. U asemblerskom jeziku nema strogog pojma tipa. Kako se podaci interpretiraju zavisi od programera. Jezik C definiše predznačene i nepredznačene cijele brojeve. To omogućuje C kompajleru da odredi prave instrukcije za upotrebu s podacima.

2.5.4.Smanjenje veličine podataka

U asemblerskom jeziku, podaci imaju specificiranu veličinu. Česta je potreba da se promijeni veličina podataka radi upotrebe s drugim podacima. Najjednostavnije je smanjenje veličine.

Da bi se smanjila veličina podataka, prosto se uklanjaju značajniji bitovi podataka. Evo trivijalnog primjera:

mov ax, 0034h ; ax = 52 (smjesten u 16 bita)mov cl, al ; cl = nizih 8 bita registra ax

16

Page 17: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Naravno, ako se broj ne može korektno predstaviti u manjoj veličini, smanjenje veličine neće raditi. Na primjer, ako je AX bio 0134h (ili 308 dekadno) gornji primjer bi i dalje postavio registar CL na 34h, što sada nije tačno.

Pravilo za nepredznačene brojeve je da svi vodeći bitovi moraju biti nule da bi se mogli ukloniti, a da rezultat i dalje bude ispravan. Pravilo za predznačene brojeve je da svi vodeći biti moraju biti ili sve nule ili sve jedinice da bi se mogli ukloniti. Pored toga, prvi neuklonjeni bit mora imati istu vrijednost kao oni uklonjeni. Taj bit predstavlja novi predznak smanjene vrijednosti. Važno je da je on jednak originalnom bitu predznaka.!

2.5.5.Povećanje veličine podataka

Povećanje veličine podataka je znatno složenije nego smanjenje. Uzeće se za primjer bajt FF. Ako se proširi na riječ, koju vrijednost bi trebao imati? To zavisi kako se FF interpretira. Ako je FF nepredznačeni bajt (255 dekadno), vrijednost u formatu riječi treba biti 00FF; ipak, ako je predznačeni bajt (-1 dekadno), riječ treba biti FFFF.

U opštem slučaju, za proširenje nepredznačenog broja, svi novi biti se prave pomoću dodavanja cifre 0. Tako FF postaje 00FF. Međutim, proširenje predznačenog broja zahtijeva proširenje bitova predznaka. To znači da novi bitovi postaju kopije bita predznaka. Kako je bit predznaka broja FF jednak 1, novi bitovi također moraju imati vrijednost 1, što stvara rezultat FFFF. Za predznačeni broj 5A (90 u dekadnom) proširenje rezultira brojem 005A.

2.6.Brojevi u pokretnom zarezu

2.6.1.Necijeli binarni brojevi

Jasno je da mora postojati način da se i necijeli brojevi predstavljaju u drugim bazama, baš kao u dekadnom sistemu. U dekadnom sistemu, cifre desno od decimalnog zareza (na računaru zbog engleskog pravopisa se koristi decimalna tačka) su povezane s negativnim stepenima broja 10.

0.123= 1 × 10-1 + 2 × 10-2 + 3 × 10-3

I binarni brojevi se mogu, stoga, slično predstaviti.

0.1012= 1 × 2-1 + 0 × 2-2 + 1 × 2-3 = 0.625

Kombiniranjem ovih ideja može se dobiti realni broj:

110.0112= 4 + 2 + 0.25 + 0.125 = 6.375

Konverzija iz dekadnog u binarni sistem nije previše teška. Principijelno, dekadni broj se podijeli u dva dijela, cijeli i razlomljeni. Konverzija cijelog dijela u binarni sistem se obavlja koristeći standardne metode. Razlomljeni dio se prevodi koristeći metod dolje opisan.

1. Pomnoži razlomljeni dio sa 2

2. Rezultat množenja može imati kao cijeli dio 1 ili 0. Zapiši tu cifru kao sljedeću decimalu binarnog broja

3. Ukloni cijeli dio rezultata, i to je nova vrijednost razlomljenog dijela

4. Ponovi postupak vrativši se na korak 1 sve dok se ne postigne potreban broj bitova.

Na Sl. 5se vidi kako se konvertira 0.5625 u binarni. Metod se zaustavlja ako se dostigne rezultat 0. 0.5625× 2 = 1.125 prvi bit = 10.125× 2 = 0.25 drugi bit = 00.25× 2 = 0.5 treći bit = 00.5× 2 = 1.0 četvrti bit = 1

17

Page 18: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Sl. 5 Konverzija 0.5625 u binarni

Kao drugi primjer, pogledati konverziju broja 23.85 u binarni. Dakle, lako je konvertirati cijeli dio (23 = 101112), ali šta sa razlomljenim dijelom (0.85)? Sl. 6 prikazuje početak tog računa.

0.85× 2 = 1.70.7× 2 = 1.40.4× 2 = 0.80.8× 2 = 1.60.6× 2 = 1.20.2× 2 = 0.40.4× 2 = 0.80.8× 2 = 1.6

Sl. 6 Konverzija 0.85 u binarni

Pažljiv pogled na brojeve otkriva beskonačnu petlju. To znači da je 0.85 periodični binarni broj (slično periodičnim decimalnim brojevima u bazi 10). Postoji uzorak kod brojeva u računu. Pogledom na ovaj uzorak se vidi da je 0.85 ≈ 0.1101102. Tako, 23.85 ≈ 10111.1101102.

Posljedica gornjeg računa je da se 23.85 ne može tačno predstaviti u binarnom sistemu upotrebom konačnog broja bita. Float i double varijable u jeziku C se smještaju kao binarni brojevi. Tako, vrijednosti poput 23.85 ne mogu se tačno smjestiti u te varijabla. Samo aproksimacija broja 23.85 se može smjestiti.

Radi pojednostavljenja hardvera, brojevi u pokretnom zarezu se čuvaju u konzistentnom formatu. Taj format koristi naučnu notaciju (ali binarno, koristeći stepene broja 2, a ne 10). Na primjer, 23.85 ili 10111.11011001100110 . . .2 se može smjestiti kao:

1.011111011001100110...× 2100

(gdje je eksponent (100) u binarnom formatu). Normalizirani broj u pokretnom zarezu ima oblik: 1.ssssssssssssssss × 2eeeeeee

gdje je 1.sssssssssssss signifikant a eeeeeeee je eksponent.

2.6.2.IEEE predstavljanje pokretnog zareza

IEEE (Institute of Electrical and Electronic Engineers) je međunarodna organizacija koja je dizajnirala specifične binarne formate za smještanje brojeva u pokretnom zarezu. Taj se format koristi na većini (ali ne svim) današnjim računarima. Često je podržan hardverom samog računara. Na primjer, Intelov numerički koprocesor (od 486 i Pentiuma ugrađen u CPU) ga koristi. IEE definiše dvije preciznosti, prostu i dvostruku. Prosta se koristi za float varijable u jeziku C, a dvostruka za double varijable.

Intelov numerički koprocesor koristi i treću preciznost, proširenu. Ona se interno koristi za sve operacije, a kada se podatak smješta u memoriju automatski se konvertuje u prostu ili dvostruku preciznost.

2.7.Stek

Mnogi mikroprocesori imaju ugrađenu podršku za stek. Stek je Last-In First-Out (LIFO ) struktura. Prostor steka u memoriji je organizovan na ovaj način. PUSH instrukcija dodaje podatke na stek, a POP instrukcija uklanja zadnji dodati podatak sa steka. Uklonjen podatak je uvijek zadnji koji je dodat. (zato se zove last-in first-out lista).

SS segmentni registar određuje segment koji sadrži stek (obično je danas to isti segment u kome su podaci). ESP registar sadrži adresu podataka koja bi bila uklonjena sa steka. Za podatke se kaže da su na vrhu steka. Podaci se u 386 zaštićenom režimu rada mogu dodati samo u jedinicama duplih riječi. To znači da se na stek ne može smjestiti pojedinačni bajt.

18

Page 19: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3. Mašinski kod i njegovo generisanje

3.1.Mašinski jezik

Svaki tip mikroprocesora razumije svoj vlastiti mašinski jezik. Instrukcije u mašinskom jeziku su brojevi smješteni kao bajtovi u memoriji. Svaka instrukcija ima svoj jedinstveni numerički kod koje se kraće zove operacijski kod, Za 80x86 procesor dužina instrukcija varira od instrukcije do instrukcije. Mnoge instrukcije također uključuju podatke (na primjer konstante ili adrese) koje instrukcija koristi.

Dosta je teško direktno programirati u mašinskom jeziku. Dešifriranje značenja numerički kodiranih instrukcija je teško za čovjeka. Na primjer, instrukcija koja kaže da se saberu registri EAX i EBX i rezultat ponovo smjesti u EAX se kodira sljedećim heksadekadnim kodovima:

03 C3

Ovo se ne može zvati očiglednim. Na sreću, postoji program koji se zove asembler, koji ovaj teški zadatak obavlja za programera.

3.2.Asemblerski jezik

Asemblerski jezik smješta program kao tekst, kao i jezici višeg nivoa. Svaka asemblerska instrukcija predstavlja tačno jednu mašinsku instrukciju. Na primjer, gore opisana instrukcija sabiranja se može predstaviti kao:

add eax, ebx

Ovdje je značenje instrukcije znatno jasnije neog u mašinskom jeziku. Riječ add je mnemonička skraćenica za instrukciju sabiranja (addition). Opšti oblik asemblerske instrukcije je:

mnemonic operand(s)

Asembler je program koji čita tekstualnu datoteku sa asemblerskim instrukcijama i prevodi asemblerski u mašinski kod. Kompajleri su programi koji rade sličnu konverziju za jezik visokog nivoa. Asembler je obično jednostavniji od kompajlera. Svaka asemblerska naredba se direktno prevodi u jednu mašinsku instrukciju. Naredbe programskog jezika visokog nivoa su složenije i zahtijevaju više mašinskih instrukcija. Druga važna razlika je što svaki različiti tip mikroprocesora ima vlastiti mašinski jezik, pa time i asemblerski jezik. Prenošenje programa između različitih arhitektura je mnogo teže nego u jezicima visokog nivoa.

U ovoj skripti se koristi Netwide Assembler ili NASM. On se može besplatno naći na Internetu. Drugi popularni asembleri su Microsoft Assembler (MASM) ili Borland Assembler (TASM). Postoje određene sintaksne razlike između MASM, TASM i NASM.

3.3.Osnovne instrukcije

Najosnovnija instrukcija je MOV instrukcija. Ona premješta podatke s jedne lokacije na drugu (poput operatora dodjeljivanja u jezicima visokog nivoa), Ona ima dva operanda:

mov dest, src

Podaci navedeni u src se kopiranju u dest. Ograničenje je da oba operanda ne mogu biti memorijski operandi. To pokazuje na još jednu nezgodnu osobinu asemblerskog jezika. Česta su (ponekad proizvoljna) pravila kako se pojedine instrukcije koriste. Operandi moraju takođe biti iste veličine. Vrijednost registra AX se ne može smjestiti u BL. Evo primjera (komentar se smješta iza oznake tačka-zarez):

19

Page 20: Sva predavanja na etfu iz predmeta kojeg drzi ribic

mov eax, 3 ; smjesti 3 u EAX registar (3 je neposredni operand)mov bx, ax ; smjesti vrijednost AX registra u BX registar

ADD instrukcija se koristi za sabiranje cijelih brojeva.

add eax, 4 ; eax = eax + 4add al, ah ; al = al + ah

SUB instrukcija se koristi za oduzimanje cijelih brojeva.

sub bx, 10 ; bx = bx - 10sub ebx, edi ; ebx = ebx - edi

Instrukcije INC i DEC umanjuju ili uvećavaju vrijednosti za 1. Pošto je “1” implicitni operant, mašinski kod za INC i DEC je kraći od ekvivalentnog za ADD i SUB instrukcije.

inc ecx ; ecx++dec dl ; dl-

3.4.Direktive

Direktiva je element asemblera, a ne mikroprocesora. One se generalno koriste bilo da daju instrukciju asembleru da nešto uradi, bilo da se asembler informira o nečemu. Direktive se ne prevode u mašinski kod. Uobičajena primjena direktiva je:

• definiranje konstanti• definiranje memorije za smještanje podataka• grupiranje memorije u segmente• uslovno uključivanje izvornog koda• uključivanje drugih datoteka

NASM program prolazi kroz predprocesor kao i program u C-u. On ima iste predprocesorske komande kao i C. Ipak, NASM predprocesorske directive počinju s znakom % umjesto # kao u jeziku C.

3.4.1.Direktiva equ

Direktiva equ se koristi za definiciju simbola. Simboli su imenovane konstante koje se korise u asemblerskom programu. Format je:

symbol equ value Vrijednosti simbola se ne mogu kasnije redefinirati.

3.4.2.Direktiva %define

Ova direktiva je slična direktivi #define u C. Ona se najčešće koristi za definisanje konstantnih makroa kao u C-u.

%define SIZE 100 mov eax, SIZE

Gornji primjer pokazuje kako se definiše makro konstanta SIZE i koristi u MOV instrukciji. Makroi su fleksibilniji nego simboli iz dva razloga. Makroi se mogu redefinirati i mogu biti nešto daleko moćnije od prostih konstantnih brojeva (npr. mogu zamijeniti čitave sekvence instrukcija).

20

Page 21: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.4.3.Direktive podatakaDirektive podataka se koriste u segmentima podataka da definišu prostor u memoriji. Imaju dva načina kako se memorija može rezervirati. Prvi način samo definiše prostor za podatke, drugi način definiše prostor i inicijalnu vrijednost. Prva metoda koristi jednu od RESX direktiva. X se zamjenjuje slovom koji određuje veličinu objekta koji se smješta. Sve moguće vrijednosti slova X su date na Sl. 7.

Jedinica memorija SlovoByte BWord Wdouble word Dquad word Qten bytes T

Sl. 7: Slova X RESX za DX directive.

Druga metoda, (koji također definiše inicijalnu vrijednost) koristi jednu od DX direktiva. X slova su ista

kao kod RESX direktiva. Vrlo je uobičajeno da se memorijske lokacije označavaju labelama. Labele omogućavaju prosto referiranje memorijskih lokacija u kodu. Ispod se nalaze primjeri:

L1 db 0 ; bajt s labelom L1 i inicijalnom vrijednosti 0L2 dw 1000 ; riječ s labelom L2 i inicijalnom vrijednosti 1000L3 db 110101b ; bajt inicijaliziran na binarno 110101 (53 dekadno)L4 db 12h ; bajt inicijaliziran heksadekadno 12 (18 dekadno)L5 db 17o ; bajt inicijaliziran na oktalno 17 (15 dekadno)L6 dd 1A92h ; dupla riječ inicijalizirana na heksadekadno 1A92L7 resb 1 ; 1 neinicijalizirani bajt L8 db "A" ; bajt inicijaliziran na ASCII kod za slovo A (65)

Dupli navodnici i jednostruki navodnici se jednako tretiraju. Susjedni podaci u memoriji se smještaju sekvencijalno u memoriji. To jest, riječ L2 je smještena odmah nakon L1 u memoriji. Moguće je podatke povezati zarezima unutar iste db instrukcije.

L9 db 0,1,2,3 ; definira 4 bajtaL10 db "r", "u", "k", ’a’, 0 ; definira a C string = "ruka"L11 db ’ruka’, 0 ; kao u L10

Direktiva DD se može koristiti za pravljenje cjelobrojnih i konstanti u prostoj preciznosti. Direktiva DQ se može koristiti samo kod konstanti u pokretnom duple preciznosti.

Za velike sekvence, često se koristi NASM ova TIMES direktiva. Ova direktiva ponavlja svoj operand određeni broj puta. Na primjer

L12 times 100 db 0 ; ekvivalent da se 100 puta navede (db 0)L13 resw 100 ; rezervira prostor od 100 riječi

Treba zapamtiti da se labele mogu koristiti za referenciranje podataka u kodu. Imaju dva načina gdje se labela može koristiti. Ako se koristi prosta labela ona se interpretira kao adresa (ili pozicija) podataka. Ako je labela smještena unutar uglatih zagrada ([ ]), interpretira se kao podatak na adresi. Drugim riječima, labela se može posmatrati kao pokazivač na podatak, a uglate zagrade kao vrijednost na lokaciji na koju pokazivač pokazuje, baš kao operator * u jeziku C. (MASM i TASM prate drugačiju konvenciju.) U 32-bitnom režimu, adrese su 32-bitne. Evo nekih primjera: mov al, [L1] ; Kopiraj bajt na lokaciji L1 u ALmov eax, L1 ; EAX = adresa bajta na lokaciji L1mov [L1], ah ; kopiraj AH u bajt na lokaciji L1mov eax, [L6] ; kopiraj duplu riječ na lokaciji L6 u EAXadd eax, [L6] ; EAX = EAX + dupla riječ na L6add [L6], eax ; dupla riječ na L6 += EAXmov al, [L6] ; kopiraj prvi bajt duple riječi na L6 u AL

21

Page 22: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Posljednja linija ovih primjera pokazuje važno svojstvo NASM-a. Asembler ne prati tip podataka na koji labela pokazuje. Na programeru je da koristi pravilno labelu. Uobičajeno da se adresa smjesti u registar i koristi registar kao pokazivačka varijabla u C-u. Još jednom, ne vrši se provjera da se pokazivač koristi pravilno. Stoga je asembler ranjiviji na greške nego C. Šta se dešava prilikom sljedeće instrukcije?:

mov [L6], 1; smjestiti 1 na L6

Ova naredba rezultira greškom da veličina nije navedena. Zašto? Zato što asembler ne zna da li da smjesti 1 kao bajt, riječ ili duplu riječ. Da bi se riješio taj problem, treba dodati oznaku veličine

mov dword [L6], 1; smjesti 1 na L6

To kaže asembleru da smjesti konstantu 1 na duplu riječ koja se nalazi na labeli L6. Druge oznake veličine su: BYTE, WORD, QWORD i TWORD.

Danas je neobično praviti samostalni program pisan kompletno u asemblerskom jeziku. Asembler se obično koristi u ključnim kritičnim rutinama. Zašto? Mnogo je lakše programirati u jeziku visokog nivoa nego u asemblerskom jeziku. Također, upotreba asemblerskog jezika čini program teškim za portiranje na druge platforme. Zapravo, asembler se rijetko koristi.

Zašto ga onda uopšte učiti?

1. Često je kod napisan u asembleru brži i kraći od kompajlerski generiranog koda.

2. Asembler omogućava pristup direktnim hardverskim osobinama sistema koje je teško ili nemoguće koristiti iz višeg programskog jezika.

3. Učenje programiranja u asembleru pomaže boljem razumijevanju kako računar radi.

4. Učenje programiranja u asembleru pomaže boljem razumijevanju kako kompajleri i jezici višeg nivoa poput C-a rade.

Ove dvije zadnje tačke demonstriraju da znanje asemblera može biti upotrebljivo čak i ako se u njemu ne programira.

3.5.Prvi program

Početni programi u ovom tekstu kreću od programa u C-u, kao na Sl. 8. Ovaj program poziva drugu funkciju, koja se zove asm_main. To je rutina koja će biti pisana u asemblerskom programu. Ima prednosti u upotrebi ovog C programa. Prvo, to prepušta C-sistemu da postavi program kako bi se on pravilno izvršavao u zaštićenom režimu rada. Sve segmente i segmentne registre će inicijalizirati C, a asemblerski kod ne treba brinuti o tome. Drugo, C biblioteka se može koristiti na ovaj način i iz asemblerskog koda. Napisane ulazno izlazne rutine date na Sl. 9 koriste prednosti ove tehnike. One koriste C-ove ulazno-izlazne funkcije (printf, itd.).

int main(){int ret_status ;ret_status = asm_main();return ret_status ;}

Sl. 8 driver.c program

print_int štampa na ekran vrijednost cijelog broja koja je smještena u EAXprint_char štampa na ekran znak čija je ASCII vrijednost koja je smještena u ALprint_string štampa na ekran sadržaj niza znakova na adresi koja je smještena u EAX. String

mora biti string C tipa (t.j. završen nulom).print_nl štampa na ekran novi red.read_int Čita cijeli broj sa tastature i smješta ga u EAX registar.read_char Čita znak sa tastature i smješta njegov ASCII kod u EAX registar.

22

Page 23: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Sl. 9: asemblerske rutine

Sljedeći primjer pokazuje prosti asemblerski program.; file: first.asm;Prvi asemblerski program. Ovaj program pita za dva cijela broja i tampa njihovu sumu; Da se kreira izvrsivi program koristeci djgpp:; nasm -f win32 first.asm; gcc -o first first.obj driver.c asm_io.obj%include "asm_io.inc";; inicializirani podaci se smještaju u .data segment;segment .data;; Ove labele prikazuju stringove za ispis;prompt1 db "Unesite broj: ", 0 ; obavezna nula na krajuprompt2 db "Unesite drugi broj: ", 0outmsg1 db "Unijeli ste ", 0outmsg2 db " i ", 0outmsg3 db ", a njihova suma je ", 0; Neinicijalizirani podaci se smještaju u .bss segment;segment .bss;; Ove labele pokazuju na cijele brojeve čije vrijednosti unosimo;input1 resd 1input2 resd 1;; programski kod se smješta u .text segment;segment .textglobal _asm_main_asm_main:

enter 0,0 ; pripremi rutinupushamov eax, prompt1 ; odstampaj porukucall print_stringcall read_int ; unesi cijeli brojmov [input1], eax ; smjesti u input1mov eax, prompt2 ; odstampaj porujucall print_stringcall read_int ; unesi cijeli brojmov [input2], eax ; smjesti u input2mov eax, [input1] ; eax = dupla riječ na input1add eax, [input2] ; eax += dupla riječ na input2mov ebx, eax ; ebx = eax; Stampanje rezultatamov eax, outmsg1call print_string ; odstampaj prvu porukumov eax, [input1]call print_int ; odstampaj input1mov eax, outmsg2call print_string ; odstampaj drugu porukumov eax, [input2]call print_int ; odstampaj input2mov eax, outmsg3 ; odstampaj trecu poruku

call print_string mov eax, ebx call print_int ; odstampaj sumu (ebx) call print_nl ; novi red popa mov eax, 0 leave ret; return back to C

23

Page 24: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Linija segment .data programa definiše sekciju programa koja će se smjestiti u segment podataka (čije je ime .data). Samo inicijalizirani podaci se mogu definisati u ovom segmentu. Ispod definicije ovog segmenta nalazi se nekoliko nizova znakova. Kako će se oni štampati bibliotekom, moraju biti završeni ASCII kodom 0. Velika je razlika između 0 i ‘0’, jer nula pod navodnicima predstavlja broj 48.

Neincijalizirani podaci se smještaju u segment .bss. Ovaj segment je dobio ime od ranog UNIX asemblerskog operatora koji znači “block started by symbol.” Postoji i segment steka. O njemu će kasnije biti riječi.

Kodni segment se iz historijskih razloga imenuje .text. U njemu se smještaju instrukcije. Primijetiti da labela “_asm_main” ima prefiks linije podvlačenja. To je dio C konvencije poziva. Vrlo je važno poznavati ovu konvenciju kada se povezuju C i asembler. Kasnije će cijela konvencija biti prikazana, ipak za sada treba reći da svi javni simboli (tj. funkcije i globalne varijable) imaju podvlačilicu koju je dodao C kompajler. (Ovo pravilo je specifično za DOS/Windows, Linuxov C kompajler ne pridodaje ništa C imenima.)

Direktiva global kaže da se labela “asm_main” proglasi globalnom. Za razliku od C-a, labele se podrazumijevaju da imaju interni opseg. To znači da samo kod unutar istog modula može koristiti labelu. Direktiva global daje eksterno vidljiv opseg specificiranoj labeli ili labelama. Modul asm_io deklariše print_int i ostale labele da budu globalne. Stoga se one mogu koristiti u modulu first.asm.

3.5.1.Kompajlerske specifičnosti

Gornji primjer je specifičan za besplatni GNU bazirani mingw gcc C/C++ kompajler. Ovaj kompajler se može slobodno preuzeti sa Interneta. On zahtijeva 386 ili bolji PC i izvršava se pod operativnim sistemima DOS, Windows 95/98 ili NT. Ovaj kompajler koristi objektne datoteke u formatu COFF (Common Object File Format). Da bi se asembliraro u ovom formatu, treba koristiti -f coff opciju sa nasm-om. Ekstenzija rezultujuće objektne datoteke će biti o. Ako se prevodi za format win32, tada se navodi opcija –f win32 (kako je navedeno u komentarima gornjeg koda) a ekstenzija objektne datoteke će biti obj.

Linux C kompajler je također GNU kompajler. Da gornji primjer radi pod Linuxom, treba prosto ukloniti podvlačilice “asm_main” na dva mjesta. Linux koristi format ELF (Executable and Linkable Format) za objektne datoteke. Stoga se za Linux treba koristiti opcija -f elf.

Borland C/C++ je još jedan popularan kompajler. On koristi Microsoft OMF format za objektne datoteke. Opcija nasm-a za Borlandove kompajlere je -f obj. Ekstenzija objektnih datoteka je obj. OMF format koristi drugačije segmentne direktive nego drugi objektni formati. Tako se linija segment .data mora promijeniti u:

segment DATA public align=4 class=DATA use32

Linija segment .bss se mora promijeniti u

segment BSS public align=4 class=BSS use32

Linija segment .text se mora promijeniti u segment TEXT public align=1 class=CODE use32

Prije ove linije dodati još jednu liniju

group DGROUP BSS DATA

Microsoft C/C++ kompajler može koristiti i OMF format i Win32 format za objektne datoteke. (Ako mu se pridruži OMF format, on interno prevodi informacije u Win32 format.) Win32 format dopušta definiciju segmenata kao za DJGPP i Linux. Za izlaz u ovom obliku koristi se opcija -f win32 . Ekstenzija je obj.

3.5.2.Asembliranje programa

Prvi korak je asemblirati program. Iz komandne linije treba otkucati

nasm -f object-format first.asm

24

Page 25: Sva predavanja na etfu iz predmeta kojeg drzi ribic

gdje je object-format bilo coff, elf, obj ili win32 zavisno od toga koji se C kompajler koristi. (Podsjetiti se da se i izvorni program mora promijeniti za Linux ili Borlandov C kompajler.)

3.5.3.Kompajliranje C programaTreba kompajlirati datoteku driver.c koristeći C kompajler. Za mingw gcc, koristi se: gcc -c driver.c

Opcija -c znači da se samo kompajlira, bez pokušaja linkovanja. Ista opcija radi i za Linux, Borland i Microsoft C kompajlere. Podrazumijevana izlazna ekstenzija za gcc kompajler je “.o”. Za NASM je ova ekstenzija .obj, ali oba formata su međusobno kompatibilna.

3.5.4.Linkovanje objektnih programa

Linkovanje je proces kombinovanja mašinskog koda i podataka u objektnim datotekama i bibliotekama s ciljem pravljenja izvršne datoteke. Kako će biti kasnije prikazano, ovaj proces je komplikovan.

C programi zahtijevaju standardnu C biblioteku i poseban startni program za izvršavaje. Mnogo je lakše reći C kompajleru da pozove linker s korektnim parametrima, nego pokušati direktno zvati linker. Na primjer, da se linkuje prvi program koristeći mingw gcc, može se pozvati:

gcc -o first driver.o first.obj asm_io.obj

Ovo kreira izvršni program koji se zove first.exe (ili samo first pod Linuxom). Za Borlandov kompajler bi se koristilo:

bcc32 first.obj driver.obj asm_io.obj

Borland koristi ime prve datoteke da odredi ime izvršne datoteke, Tako u gornjem primjeru, program bi dobio ime first.exe. Moguće je kombinovati fazu kompajliranja i linkovanja. Na primjer

gcc -o first driver.c first.obj asm_io.obj

Sada će gcc kompajlirati driver.c i linkovati ga.

3.5.5.Razumijevanje asemblerskog listinga

Opcija -l listing-file se može koristiti da se kaže nasm-u da koristi listing navedenog imena. Ova datoteka pokazuje kako je kod asembliran. Evo primjera kako se dvije linije iz segmenta podataka izvornog koda prenose u listing datoteku. (Brojevi linija su u datoteci listinga, ali brojevi linija u izvornoj datoteci ne moraju biti jednaki brojevima linija u listing datoteci.) 50 00000024 556E696A656C692073- outmsg1 db "Unijeli ste ", 051 0000002D 74652000 52 00000031 20692000 outmsg2 db " i ",

Prva kolona u svakoj liniji je linijski broj, a druga je pozicija (heksadekadno) podataka u segmentu. Treća kolona predstavlja heksadekadne vrijednosti koje će biti smještene. U ovom slučaju to su ASCII kodovi slova. Na kraju se nalazi izvorni tekst programa. Pozicije iz druge kolone vrlo vjerovatno neće biti finalne, prave vrijednosti adresa u memoriji u konačnom programu. Svaki modul može definisati svoje vlastite labele u segmentu podataka (kao i u drugim segmentima). U fazi linkovanja, sve ove definicije labela u segmentu podataka se spajaju u jedinstveni segment podataka. Nove, finalne pozicije se onda računaju pomoću linkera.

Evo još jednog odsječka listing datoteke u okviru text segmenta.

81 0000002D A1[00000000] mov eax, [input1];eax=dupla riječ na input182 00000032 0305[04000000] add eax, [input2];eax+=dupla riječ na input283 00000038 89C3 mov ebx, eax ; ebx = eax

Treća kolona pokazuje mašinski kod koji generiše asembler. Često se ne može još znati kompletan kod z instrukciju. Na primjer, u 81-oj liniji pozicija za input1 još nije poznata prije linkovanja programa. Asembler

25

Page 26: Sva predavanja na etfu iz predmeta kojeg drzi ribic

može izračunati operacioni kod za mov instrukciju, iz listinga se to vidi da je A1, ali memorijska adresa piše u ugaonim zagradama, jer se njena vrijednost još ne može izračunati. U ovom se slučaju postavi privremena vrijednost 0, jer je input1 na početku bss segmenta. Kada se program povezuje, linker će ubaciti prave adrese na dato mjesto. Druge instrukcije, kao linija 83 ne referencira nikakve labele, pa ovdje asembler može odmah da generiše korektan kod.

3.5.6.Big i Little Endian predstavljanje

Ako se pogleda linija 82 iz listing datoteke, primjećuje se nešto čudno vezano za adresu u uglastim zagradama. Labela input2 je na relativnoj poziciji 4 (kako je definisano u ovoj datoteci); ipak, vrijednost koja se pojavljuje u memoriji nije 00000004, nego 04000000. Zašto? Različiti procesori smještaju višebajtne cijele brojeve različitim redoslijedom u memoriji. Imaju dva popularna metoda za smještanje cijelih brojeva, big endian i little endian. Big endian je metod koji izgleda prirodnije. Najveći (tj. najznačajniji) bajt se smješta na adresu najmanju po vrijednosti, nakon njega sljedeći po značaju itd. Na primjer dupla riječ 00000004 se smješta kao četiri bajta, tj. 00 00 00 04. IBM veliki računari, većina RISC procesora i Motorolini procesori koriste metod big endian. Ipak, Intel-bazirani procesori koriste little endian metod! Ovdje se najmanje značajan bajt smješta prvi, pa se 00000004 smješta u memoriji kao 04 00 00 00. Razlog za ovaj format je što operacije s Little endian formatom mogu ponekad biti brže. Ovaj format je ugrađen u sam mikroprocesor i ne može se mijenjati. Normalno programer ne treba da brine koji se format koristi. Ipak, ponekad je ovo bitno

1.Kada se binarni podaci prenose između različitih računara (preko datoteka ili mrežno) 2.Kada se binarni podaci upisuju u memoriju kao višebajtni cijeli broj, a zatim čitaju kao pojedinačni bajtovi ili obrnuto.

Endian format se ne odnosi na redoslijed čitavih brojeva unutar nizova. Prvi element niza je uvijek na najnižoj adresi. To važi i za stringove (koji su nizovi znakova). Endian format se, međutim, odnosi na pojedinačne elemente nizova.

3.5.7.Uzorak za pisanje programa

Sl. 10 pokazuje datoteku uzorka, koja se može koristiti kao startna tačka za pisanje asemblerskih programa, povezanih s glavnim C programom.%include "asm_io.inc"segment .data; inicijalizirani podaci se smještaju u data segment;segment .bss;; neincijalizirani podaci se smještaju u bss segment;segment .textglobal _asm_main_asm_main:enter 0,0 ; pocetakpusha;; program se smješta u text segment. ; Ne mijenjajte dio koda napisan u uzorku.;popamov eax, 0 ; povratak u Cleaveret

Sl. 10 Uzorak za programe koji koriste C biblioteku

26

Page 27: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Da bi se mogli praviti samostalni programi u asemblerskom jeziku, pod operativnim sistemom MS Windows, bez povezivanja sa programskom bibliotekom jezika C, datoteka s uzorkom može izgledati ovako:

; ----------------------------------------------------------------------------; prazan.asm;; Ovo je osnovna kopija programa koji ne koristi C biblioteku; pa izadje.; On koristi fukciju ExitProcess iz biblioteke kernel32;; Assembler: NASM; OS: Any Win32-based OS; Other libraries: Use gcc's import library libkernel32.a; Assemblirajte sa nasm -fwin32 prazan.asm; Linkovanje sa ld –o prazan.exe -e go hello.obj c:\devcpp\lib\libkernel32.a; (uz pretpostavku da se biblioteka libkernel32.a nalazi u c:\devcpp\lib; ----------------------------------------------------------------------------

global goextern _ExitProcess@4

section .data

section .textgo:

; ExitProcess(0)push dword 0call _ExitProcess@4

Ovaj uzorak-program za pravljenje samostalnih Windows aplikacija bazira se na upotrebi jedne veoma važne sistemske datoteke, koja se zove KERNEL32.DLL. Unutar ove datoteke se nalazi ExitProcess funkcija koja prekida program koji ju je pozvao.

27

Page 28: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.6.Instrukcije za proširenje veličine podataka

Ima više instrukcija za procesor 80386 i jači koje omogućavaju proširivanje veličine brojeva. Sam računar ne zna da li je broj predznačeni ili nepredznačeni. Na programeru je da koristi korektnu instrukciju.

Za nepredznačene brojeve mogu se prosto staviti nule u gornje bitove koristeći instrukciju MOV. Na primjer, da se proširi bajt u AL u nepredznačenu riječ u AX:

mov ah, 0 ; zero out upper 8-bits

Ipak, nije moguće koristiti MOV instrukciju za konverziju nepredznačene riječi u AX u nepredznačenu duplu riječ u EAX. Zašto? Nema načina da se MOV instrukcijom specificira gornjih 16 bita registra EAX. Procesor 80386 rješava ovaj problem uvođenjem nove instrukcije MOVZX. Ova instrukcija ima dva operanda. Odredišni (prvi operand) mora biti 16 ili 32 bitni registar. Izvorni (drugi operand) može biti 8 ili 16 bitni registar odnosno bajt ili riječ memorije. Drugo ograničenje je da odredište mora biti veće od izvora. (Većina instrukcija zahtijeva da su izvor i odredište iste veličine.) Evo nekih primjera:

movzx eax, ax ; proširuje ax u eax uz punjenje proširenja nulamamovzx eax, al ; proširuje al u eax uz punjenje proširenja nulamamovzx ax, al ; proširuje al u ax uz punjenje proširenja nulamamovzx ebx, ax ; proširuje ax u ebx uz punjenje proširenja nulama

Za predznačene brojeve, MOV nije pogodna za proširivanje veličine podataka. 8086 procesor dao je neke instrukcije za proširivanje predznačenih brojeva. Instrukcija CBW (Convert Byte to Word) predznačno proširuje registar AL u AX. Operandi su implicitni. CWD (Convert Word to Double word) instrukcja predznačmo proširuje AX u DX_AX. Notacija DX_AX predstavlja da se zamisli da DX i AX registri čine jedan 32 bitni registar s gornjih 16 bita u DX i donjih 16 bita u AX. (Ne treba zaboraviti da 8086 nije imo ni jedan 32 bitni registar!) Procesor 80386 je dodao nekoliko novih instrukcija. Instrukcija CWDE (Convert Word to Double word Extended) predznačno proširuje AX u EAX. Instrukcija CDQ (Convert Double word to Quad word) predznačno proširuje EAX u EDX_EAX (64 bitsa). Konačno, instrukcija MOVSX radi kao MOVZX, osim što koristi pravila za predznačene brojeve.

movsx eax, al ; proširuje al u eax uz propagaciju predznaka

3.7.Aritmetičke instrukcije

Već je rečeno da add instrukcija obavlja sabiranje a sub instrukcija obavlja oduzimanje. Dva bita u FLAGS registru koje ove instrukcije postavljaju su overflow (prekoračenje) i carry (prijenos) indikator. Overflow bit se postavlja na 1 ako je pravi rezultat operacije prevelik za smještanje na odredište za predznačenu aritmetiku. Carry bit se postavlja ako se desio prijenos u najvišem bitu sabiranja ili pozajmnica u najvišem bitu oduzimanja. Tako se on može koristiti za prepoznavanje prekoračenja za nepredznačenu aritmetiku. Upotreba carry indikatora u predznačenoj aritmetici će ubrzo biti objašnjena. Velika prednost drugog komplementa je što su pravila za sabiranje i oduzimanje ista i za predznačenu i za nepredznačenu aritmetiku. Tako se i add i sub mogu koristiti i za preznačenu i nepredznačenu aritmetiku.

002C 44+ FFFF + (-1) 002B 43

Ovdje se carry generiše, ali on nije dio rezultata, pa se prosto može ignorisati.

Postoje dvije različite instrukcije za množenje i dijeljenje. Prvo, za množenje se može koristiti bilo MUL ili IMUL instrukcija. Instrukcija MUL se koristi za množenje nepredznačenih brojeva, a instrukcija IMUL se koristi za množenje predznačenih brojeva. Zašto su potrebne dvije različite instrukcije? Pravila za množenje su različita za nepredznačene i predznačene brojeve u drugom komplementu. Uzeti za primjer množenje bajta FF sa samim sobom i posmatrati rezultat koji je 16-bitna riječ. Nepredznačeno množenje daje 255 puta

28

Page 29: Sva predavanja na etfu iz predmeta kojeg drzi ribic

255 ili 65025 (odnosno FE01 u heksadekadnom sistemu). Upotreba predznačenog množenja daje -1 puta -1 ili 1 (odnosno 0001 heksadekadno). Ima više oblika instrukcija množenja. Najstariji oblik izgleda kao:

mul source

Source (izvor) može biti registar ili memorijska lokacija. Ne može biti neposredna vrijednost. Koja se tačno operacija množenja obavlja zavisi od veličine izvornog operanda. Ako je operand veličine bajta, množi se taj bajt sa sadržajem registra AL i rezultat se smješta u 16 bita registra AX. Ako je izvor 16-bitni, množi se kao riječ sa sadržajem registra AX a 32-bitni rezultat smješta u DX_AX. Ako je izvor 32-bitni, množi se sa EAX i 64-bitni rezultat smješta u EDX_EAX.

Instrukcija IMUL ima isti format kao i MUL, ali dodaje još neke formate. Postoje formati od dva ili tri operanda:

imul dest, source1 imul dest, source1, source2

Sl. 11 prikazuje sve moguće kombinacijeDest source1 source2 Actionreg/mem8 AX = AL*source1reg/mem16 DX:AX = AX*source1reg/mem32 EDX:EAX = EAX*source1reg16 reg/mem16 dest *= source1reg32 reg/mem32 dest *= source1reg16 immed8 dest *= immed8reg32 immed8 dest *= immed8reg16 immed16 dest *= immed16reg32 immed32 dest *= immed32reg16 reg/mem16 immed8 dest = source1*source2reg32 reg/mem32 immed8 dest = source1*source2reg16 reg/mem16 immed16 dest = source1*source2reg32 reg/mem32 immed32 dest = source1*source2

Sl. 11: imul Instrukcije

. Dva operatora dijeljenja su DIV i IDIV. Oni obavljaju nepredznačeno i predznačeno dijeljenje. Opšti

oblik je

div source

Ako je izvor 8-bitni, AX se dijeli sa operandom. Količnik se smješta u AL a ostatak u AH. Ako je izvor 16-bitni, onda se DX_AX dijele s operandom. Količnik se smješta u AX a ostatak u DX. Ako je izvor 32-bitni, onda se EDX_EAX dijeli sa operandom, a količnik se smješta u EAX a ostatak u EDX. Instrukcija IDIV radi na isti način. Ako je količnik prevelik da stane u registre ili je djeljenik jednak 0, program se prekida i završava. Veoma česta greška je zaboravljanje inicijalizacije registara DX ili EDX prije dijeljenja. Pažnja: specijalni oblici sa slike 11 postoje samo za IMUL a ne za MUL, DIV i IDIV.

Instrukcija NEG negira svoj operand računajući njegov drugi komplement. Njen operand može biti 8-bit, 16-bit, ili 32-bit register ili memorijska lokacija

3.7.1.Primjer program

math.asm1%include "asm_io.inc"2segment .data ; Izlazne poruke3prompt db "Unesi broj: ", 0

29

Page 30: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4square_msg db "Kvadrat broja je ", 05cube_msg db "Kub broja je ", 06cube25_msg db "Kub pomnozen s 25 je ", 07quot_msg db "Kolicnik kuba sa 100 je ", 08rem_msg db "Ostatak kuba pri dijeljenju sa 100 ", 09neg_msg db "Negacija ostatka je ", 010segment .bss12input resd 113segment .text15global _asm_main16_asm_main:17enter 0,0 ; Pocetak18pusha19mov eax, prompt21call print_string22call read_int24mov [input], eax25imul eax ; edx:eax = eax * eax27mov ebx, eax ; sacuvaj rezultat in ebx28mov eax, square_msg29call print_string30mov eax, ebx31call print_int32call print_nl33

34mov ebx, eax35imul ebx, [input] ; ebx *= [input]36mov eax, cube_msg37call print_string38mov eax, ebx39call print_int40call print_nl41

42imul ecx, ebx, 25 ; ecx = ebx*2543mov eax, cube25_msg44call print_string45mov eax, ecx46call print_int47call print_nl48mov eax, ebx50cdq ; Inicijaliziraj edx prosirenjem predznaka51mov ecx, 100 ; ne moze se dijeliti konstantom52idiv ecx ; edx:eax / ecx

53mov ecx, eax ; kolicnik u ecx54mov eax, quot_msg55call print_string56mov eax, ecx57call print_int58call print_nl59mov eax, rem_msg60call print_string61mov eax, edx62call print_int63call print_nl64neg edx ; negirati ostatak

66mov eax, neg_msg

30

Page 31: Sva predavanja na etfu iz predmeta kojeg drzi ribic

67call print_string68mov eax, edx69call print_int70call print_nl71popa73mov eax, 074leave ; povratak u C75ret

3.7.2.Instrukcije proširene tačnosti

Asemblerski jezik takođe posjeduje instrukcije koje omogućavaju sabiranje ili oduzimanje brojeva većih od duple riječi. Ove instrukcije koriste carry indikator. Kako je već rečeno, i ADD i SUB instrukcije mijenjaju carry indikator ako se dese prijenos ili pozajmnica. Ova informacija smještena u carry indikator se može koristiti za sabiranje ili oduzimanje velikih brojeva razbijanjem operacije u operacije sa manjim dijelovima veličine duplih riječi (ili još manje)

Instrukcije ADC i SBB koriste ovu informaciju u carry indikatoru. Instrukcija ADC obavlja sljedeću operaciju:

operand1= operand1+ carry flag + operand2

Instrukcija SBB obavlja:operand1= operand1-carry flag - operand2

Kako se ovo koristi? Neka se žele sabrati 64-bitni cijeli brojevi u EDX_EAX sa EBX_:ECX. Sljedeći program će smjestiti sumu u EDX_EAX:

1add eax, ecx ; saberi niza 32-bita2adc edx, ebx ; saberi visa 32-bita i prijenos iz prethodne sume

Oduzimanje je vrlo slično. Sljedeći dio koda oduzima EBX_ECX od EDX_EAX:

1sub eax, ecx ; oduzmi nizih 32-bita2sbb edx, ebx ; oduzmi visih 32-bita i pozajmnicu

Za vrlo velike brojeve može se koristiti petlja. Unutar petlje je pogodno koristiti ADC instrukciju za svaku iteraciju (umjesto za svaku osim prve). To se može postići instrukcijom CLC (CLear Carry) neposredno prije petlje za inicijalizaciju carry indikatora na 0. Ako je carry indikator jednak 0, nema razlike između instrukcija ADD i ADC. Ista ideja se može koristiti i za oduzimanje.

3.8.Kontrolne strukture

Jezici visokog nivoa imaju kontrolne strukture visokog nivoa (na primjer naredbe if i while) koje kontrolišu tok izvršenja. Asemblerski jezik ne nudi takve kompleksne kontrolne strukture. Umjesto toga, on koristi nešto poput ozloglašene goto naredbe koja nepravilno korištena rezultuje špagetnim kodom. Ipak, moguće je pisati struktuirane asemblerske programe. Osnovna procedura je dizajnirati program koristeći poznate strukture iz viših programskih jezika, a zatim prevesti dizajn u odgovarajući asemblerski jezik (slično kao što bi i kompajler uradio).

3.8.1.Poređenja

Kontrolne strukture treba da odrede šta da se radi na bazi poređenja podataka. U asemblerskom jeziku, rezultat poređenja se smješta u FLAGS registar da bi se kasnije koristio. Procesori iz serije 80x86 nude

31

Page 32: Sva predavanja na etfu iz predmeta kojeg drzi ribic

instrukciju CMP radi obavljanja poređenja. Registar FLAGS se postavlja na bazi razlike dva operand instrukcije CMP. Operandi se interno oduzmu i FLAGS se postavi na bazi rezultata, ali rezultat samog oduzimanja se nigdje ne smješta. Ako je rezultat potreban, tada se treba koristiti SUB instrukcija umjesto CMP.

Za nepredznačene cijele brojeve postoje dva indikatora (engl. Flag) (bita u registru FLAGS) koja su značajna, to su zero (ZF) i carry (CF) flag. Indikator nule (zero) se postavlja na 1 ako bi rezultat razlike bio 0. Indikator carry se koristi kao bit pozajmnice kod oduzimanja. Slijedi primjer poređenja:

cmp vleft, vright

Razlika između vleft - vright se izračuna i indicatori se postave u skladu s tim. Ako je razlika koja se računala u CMP instrukciji jednaka 0, to znači da je vleft = vright, pa se ZF postavi na 1, a CF na 0. Ako je vleft > vright, onda se ZF postavlja na 0 i CF se postavlja na 0 (nema pozajmnice). Ako je vleft < vright, onda se ZF postavlja na 0, a CF na 1 (pozajmnica).

Za predznačene cijele brojeve, tri indikatora su važna: zero (ZF), overflow (OF), i sign (SF). Indikator prekoračenja (overflow) se postavlja na 1 ako je rezultat operacije neočekivanog predznaka (npr. nakon sabiranja dva pozitivna broja dobije se negativan broj), čime se došlo do prekoračenja opsega predznačenih brojeva. Indikator predznaka (sign flag) se postavlja na 1 ako je rezultat operacije negativan broj. Ako je vleft = vright, ZF se postavlja na 1 (kao za nepredznačene brojeve). Tako za CMP ako je vleft > vright, ZF je postavljen na 0 a SF = OF. Ako je vleft < vright, ZF je 0 i SF = OF.Zašto se dešava SF = OF ako je vleft > vright? Ako nema prekoračenja, razlika će imati tačnu vrijednost i ona mora biti nenegativna. Tako SF = OF = 0. Ipak, ako ima prekoračenja, razlika neće imati tačnu vrijednost (zapravo će biti negativna). Tada, SF = OF = 1.

Ne treba zaboraviti da i druge instrukcije mijenjaju registar FLAGS ne samo CMP.

3.8.2.Instrukcije grananja

Instrukcije grananja mogu prenijeti izvršenje na proizvoljnu tačku programa. Drugim riječima, ponašaju se kao instrukcija goto. Postoje dva tipa grananja, bezuslovno i uslovno. Bezuslovni skok se ponaša kao goto, skok se uvijek dešava. Uslovni skok se može i ne mora desiti zavisno od indikatora u FLAGS registru. Ako uslovni skok ne izaziva prekid, kontrola se prenosi na narednu instrukciju.

Instrukcija JMP (skraćeno od jump ) čini bezuslovne skokove. Njen jedini argument je obično programska labela na koju se treba skočiti. Asembler ili linker će zamijeniti labelu ispravnom adresom instrukcije. To je još jedna od dosadnih operacija koje asembler čini da programerov život bude jednostavniji. Važno je zaključiti da naredba iza JMP se nikada neće izvršiti ukoliko neka druga instrukcija ne skoči na nju.

Ima više varijanti instrukcija skokova:

SHORT Ovaj skok je vrlo ograničenog dometa. On može napraviti pomak svega do 128 bajtova u memoriji. Prednost ovog tipa je što koristi manje memorije nego ostali. On koristi jedan predznačeni bajt da smjesti pomak skoka. Pomak predstavlja vrijednost za koliko se bajtova treba pomjeriti naprijed ili nazad. Pomak se sabira sadržajem registra EIP). Da se specificira kratki skok, koristi se ključna riječ SHORT odmah ispred labele unutar instrukcije JMP.

NEAR Ovaj skok je podrazumijevani tip za bezuslovne i uslovne skokove, pa se može koristiti za skok na bilo koju lokaciju na segmentu. Zapravo, 80386 podržava dva tipa near skokova. Jedan od njih koristi dva bajta za pomak. To dopušta skokove do približno 32,000 bajtova. Drugi tip koristi četiri bajta za pomak, što naravno dopušta skok na bilo koju lokaciju unutar kodnog segmenta. Četvorobajtna verzija je podrazumijevana u 386 zaštićenom režimu. Dvobajtni tip se može specificirati stavljanjem WORD ključne riječi prije labele u instrukciji JMP.

FAR Ovaj skok omogućava kontroli da skoči u drugi kodni segment. To je izuzetno rijetka akcija u 386 zaštićenom režimu.

32

Page 33: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Važeće labele u kodu prate ista pravila kao labele podataka. Labele koda se definišu njihovim postavljanjem u segment koda ispred naredbe koju označavaju. Dvotačka se postavlja na kraj labele, na njenom mjestu definicije. Dvotačka nije dio imena.

Ima mnogo različitih uslovnih skokova. One također označavaju labelu kao njihov jedini operand. Najjednostavnije od njih samo posmatraju jedan od bitova FLAGS registra da odrede da li će se skok obaviti ili ne. Slijedi lista ovih instrukcija (PF je parity flag koji indicira da li donjih 8 bita rezultata ima paran ili neparan broj bitova koji imaju vrijednost jednaku 1.)

JZ skače samo ako je ZF postavljen na 1JNZ skače samo ako je ZF postavljen na 0 JO skače samo ako je OF postavljen na 1 JNO skače samo ako je OF postavljen na 0 JS skače samo ako je SF postavljen na 1JNS skače samo ako je SF postavljen na 0 JC skače samo ako je CF postavljen na 1 JNC skače samo ako je CF postavljen na 0 JP skače samo ako je PF postavljen na 1 JNP skače samo ako je PF postavljen na 0

Sl. 12 Uslovni skokovi

Sljedeći pseudokod: if ( EAX == 0 ) EBX = 1; else EBX = 2;

se može pisati u asemblerskom jeziku kao:

cmp eax, 0 ; postavi indikatore (ZF se postavi na 1 ako je eax= 0)jz thenblock ; ako je ZF=1 idi na thenblockmov ebx, 2 ; ELSE dio naredbe IFjmp next ; preskoci THEN dio naredbe IF

thenblock:mov ebx, 1 ; THEN dio naredbe IF

next:

Druga poređenja se ne mogu jednostavno postići koristeći instrukcije s Sl. 12. Za ilustraciju, pogledati sljedeći pseudokod:

if ( EAX >= 5 ) EBX = 1; else EBX = 2;

Ako je EAX veći ili jednak od 5, ZF može biti postavljen na 1 ili 0, a SF će biti jednak OF. Ovdje je asemblerski kod koji testira ove uslove, uz pretpostavku da je EAX predznačen.:

cmp eax, 5js signon ; idi na signon ako je SF = 1jo elseblock; idi na elseblock ako je OF = 1 i SF = 0jmp thenblock ; idi na thenblock ako je SF = 0 i OF = 0

signon:jo thenblock ; idi na thenblock ako je SF = 1 i OF = 1

elseblock:mov ebx, 2jmp next

thenblock:mov ebx, 1

next:

33

Page 34: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Gornji kod je dosta nezgodan. Na sreću, 80x86 posjeduje dodatne instrukcije grananja koje čine ove testove znatno jednostavnijim. Postoje predznačene i nepredznačene verzije svakog od njih. Skokovi jednakosti i nejednakosti (JE i JNE) su isti i za predznačene i nepredznačene brojeve. (Zapravo, JE i JNE su identični sa JZ i JNZ, respektivno.) Predznačeno NepredznačenoJE skočiće ako vleft = vright JE skočiće ako vleft = vrightJNE skočiće ako vleft ≠vright JNE skočiće ako vleft ≠ vrightJL, JNGE skočiće ako vleft < vright JB, JNAE skočiće ako vleft<vrightJLE, JNG skočiće ako vleft <= vright JBE, JNA skočiće ako vleft<=vrightJG, JNLE skočiće ako vleft > vright JA, JNBE skočiće ako vleft>vrightJGE, JNL skočiće ako vleft >= vright JAE, JNB skočiće ako vleft >= vright

Većina instrukcija skoka ima sinonime. Na primjer, instrukcija JL (jump less than) i JNGE (jump not greater than or equal to).

Skokovi za nepredznačeno poređenje imaju u imenu slova A za veće i B za manje umjesto L i G.

Korištenjem ovih instrukcija grananja, pseudokod se može prevesti u asembler znatno jednostavnije.

1 cmp eax, 52 jge thenblock3 mov ebx, 24 jmp next5thenblock:6 mov ebx, 17next:

3.8.3.Instrukcije petlji

80x86 ima više instrukcija kojima se mogu implementirati petlje slične FOR petlji. Svaka od njih koristi labelu koda kao svoj jedini operand.

LOOP Umanjuje ECX, ako je ECX ≠ 0, skače na labelu

LOOPE, LOOPZ Umanjuje ECX (FLAGS registar se ne mijenja),ako je ECX ≠ 0 i ZF = 1, vrši se skok

LOOPNE, LOOPNZ Umanjuje ECX (FLAGS se ne mijenja), ako je ECX ≠ 0 i ZF = 0, vrši se skok

Zadnje dvije instrukcije petlji se koriste za petlje za sekvencijalno pretraživanje. Sljedeći pseudokod: sum = 0;for ( i =10; i >0; i --)sum += i;

se može prevesti u asemblerski jezik kao:

1 mov eax, 0 ; eax je sum2 mov ecx, 10 ; ecx je i3start_petlje:4 add eax, ecx5 loop start_petlje

34

Page 35: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.9.Operacije pomijeranja bitova

Asemblerski jezik dopušta programeru da manipuliše pojedinim bitovima podataka. Jedna uobičajena operacija s bitovima se zove pomijeranje (shift). Operacija pomijeranja premješta poziciju bitova nekih podataka. Pomijeranje se može vršiti nalijevo (ka najznačajnijem bitu) ili nadesno (ka najmanje značajnom bitu).

3.9.1.Logičko pomijeranje

Logičko pomijeranje je najjednostavniji oblik pomijeranja bitova. Ono pomjera bitove na prilično razumljiv način. Na Sl. 13 se vidi primjer pomjerenog jednobajtnog broja.

Original 1 1 1 0 1 0 1 0Pomjeren ulijevo 1 1 0 1 0 1 0 0Pomjeren udesno 0 1 1 1 0 1 0 1

Sl. 13: Logička pomijeranja

Treba primijetiti da su novi, dolazeći bitovi uvijek jednaki nuli. Instrukcije SHL i SHR se koriste za obavljanje logičkih pomijeranja ulijevo i udesno. Ove instrukcije se koriste za pomijeranje ulijevo i udesno. One omogućavaju pomijeranje za proizvoljan broj pozicija. Broj pozicija za pomijeranje može biti bilo konstanta, bilo smještena u CL registar. Zadnji bit istisnut iz podataka se smješta u carry indikator. Evo nekih primjera koda:

1mov ax, 0C123H2 shl ax,1 ; pomjeri 1 bit ulijevo, ax = 8246H, CF=13shr ax, 1; pomjeri 1 bit udesno, ax = 4123H, CF=04 shr ax, 1; pomjeri 1 bit udesno, ax = 2091H, CF=15mov ax, 0C123H6shl ax, 2; pomjeri 2 bitova ulijevo, ax = 048CH, CF = 17mov cl, 38shr ax, cl ; pomjeri 3 bitova udesno, ax = 0091H, CF = 1

3.9.2.Upotreba pomijeranja

Brzo množenje i dijeljenje su najčešće primjene operacija pomijeranja. U dekadnom sistemu množenje i dijeljenje sa stepenima broja 10 su trivijalni, dovoljno je pomjerati broj (dodavanjem nula ili pomicanjem decimalnog zareza). To važi i za binarne brojeve, Na primjer, da se udvostruči binarni broj 10112 (ili 11 dekadno), treba ga pomjeriti jednom ulijevo da se dobije 101102 (ili 2210). Količnik dijeljenja sa stepenom broja 2 je rezultat pomijeranja ulijevo. Da se podijeli sa brojem 2, koristi se jedno pomijeranje udesno; za dijeljenje sa 4 (22 ), broj treba udesno pomjeriti za 2 mjesta; za djeljenje sa 8 (23), treba pomjeriti za 3 mjesta udesno, itd. Instrukcije pomijeranja su jednostavne i daleko brže od odgovarajućih MUL i DIV instrukcija! Zapravo, logička pomijeranja se mogu koristiti za množenje i dijeljenje nepredznačenih vrijednosti, One generalno ne rade za predznačene vrijednosti. Na primjer, 16-bitna vrijednost FFFF predstavlja predznačeno -1. Ako se pomjeri desno jednom, rezultat je 7FFF što predstavlja +32767! Stoga postoji drugi tip pomijeranja koji se može koristiti za predznačene vrijednosti.

3.9.3.Aritmetičko pomijeranje

Ove instrukcije pomijeranja su dizajnirane da omoguće predznačenim brojevima da budu brzo pomnoženi ili podijeljeni stepenima broja 2. To osigurava da se bit predznaka korektno tretira.

SAL Shift Arithmetic Left – Ova instrukcija je samo sinonim za SHL. Ona se prevodi u doslovno isti mašinski kod kao i SHL. Sve dok se bit predznaka ne mijenja pomijeranjem, rezultat će biti korektan.

35

PodatakC 0 SHL

Podatak C0 SHR

Page 36: Sva predavanja na etfu iz predmeta kojeg drzi ribic

SAR Shift Arithmetic Right – Ovo je instrukcija koja ne pomjera bit predznaka (najznačajniji bit) svog operanda. Drugi bitovi su pomjereni kao i normalno, osim što su novi biti koji dolaze s lijeve strane kopija bita predznaka (tj. Ako je bit predznaka jednak 1, novi biti su takođe 1). Tako, ako se bit pomjera ovom instrukcijom, samo donjih 7 bita se pomjera. Kao i sa drugim pomijeranjima, zadnji pomjereni bit se smješta u carry indikator.

1mov ax, 0C123H2sal ax, 1 ; ax = 8246H, CF = 13sal ax, 1 ; ax = 048CH, CF = 14sar ax, 2 ; ax = 0123H, CF = 0

3.9.4.Rotiranje bitova

Instrukcije rotiranja rade kao instrukcije logičkog pomijeranja, osim što se bitovi koji bi bili izgubljeni na jednoj strani ubacuju na drugu. Tako se podaci kreiraju kao cirkularna struktura. Dvije najprostije instrukcije rotiranja su ROL i ROR koje obavljaju rotiranja u lijevo i desno. Baš kao i ostala pomijeranja, ova pomijeranja ostavljaju kopiju zadnjeg kopiranog bita u carry indikator.

1mov ax, 0C123H2rol ax, 1 ; ax = 8247H, CF = 1 3rol ax, 1 ; ax = 048FH, CF = 14rol ax, 1 ; ax = 091EH, CF = 05ror ax, 2 ; ax = 8247H, CF = 16ror ax, 1 ; ax = C123H, CF = 1

Postoje dvije dodatne instrukcije rotiranja koje pomjeraju bitove u podacima i carry indikatoru zvane RCL and RCR. Na primjer, ako se rotira registar AX ovim instrukcijama, rotira se 17 bita sastavljenih od AX i carry indikatora.

1mov ax, 0C123H2clc ; obrisi carry indikator (CF = 0)3rcl ax, 1 ; ax = 8246H, CF = 14rcl ax, 1 ; ax = 048DH, CF = 15rcl ax, 1 ; ax = 091BH, CF = 06rcr ax, 2 ; ax = 8246H, CF = 17rcr ax, 1 ; ax = C123H, CF = 0

3.9.5.Primjer upotrebe instrukcija za pomijeranje i rotiranje

Ovo je dio koda koji broji koliko bitova u EAX registru imaju vrijednost 1.1mov bl, 0 ; bl ce sadrzati broj postavljenih bita2mov ecx, 32; ecx je brojac petlje

count_loop:4shl eax, 1 ; pomjeri bit u carry indikator5jnc skip_inc ; ako je CF == 0, idi na skip_inc6inc bl

skip_inc:8loop count_loop

Gornji program briše originalnu vrijednost registra EAX (EAX je nula na kraju petlje). Ako se želi sačuvati vrijednost registra EAX, linija 4 se treba zamijeniti sa rol eax, 1.

3.9.6.Bulove bit operacije

Postoje četiri Bulove bit operacije: AND, OR, XOR and NOT. Tablica istinitosti pokazuje rezultat svake opoeracije za svaku moguću vrijednost njenih operanada.

36

Podatak C

PodatakC 0

SAR

SAL

PodatakC ROL

Podatak C ROR

PodatakC RCL

Podatak C RCR

Page 37: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.9.7. AND operacija

Rezultat AND operacije nad dva bita je 1 ako su oba bita 1, inače je 0 kao što tablica na Sl. 14 pokazuje.

X Y X AND Y

0 0 00 1 01 0 01 1 1

Sl. 14: AND operacija

Procesori podržavaju ove operacije kao instrukcije koje rade neovisno nad svim bitima paralelno. Na primjer ako se izvrši AND operacija između AL i BL , osnovna AND operacija se primjeni na svaki od 8 parova odgovarajućih bita u oba registra, kako prikazuje Sl. 15 .

10101010AND 11001001

10001000

Sl. 15 operacija AND radi bit po bit

Na primjer:

1 mov ax, 0C123H2 and ax, 82F6H ; ax = 8022H

3.9.8.OR operacija

Uključivo OR dva bita je 0 ako i samo ako su oba bita 0, inače je rezultat 1 kao što pokazuje tablica istinitosti na Sl. 16 .

X Y X OR Y

0 0 00 1 11 0 11 1 1

Sl. 16: OR operacija

Na primjer:1 mov ax, 0C123H2 or ax, 0E831H ; ax = E933H

3.9.9. XOR operacija

Isključivo XOR dva bita je 0 ako i samo ako su oba bita jednaka u protivnom je rezultat 1 kao što pokazuje tablica na Sl. 17.

X Y X XOR Y

0 0 0

37

Page 38: Sva predavanja na etfu iz predmeta kojeg drzi ribic

0 1 11 0 11 1 0

Sl. 17 XOR operacija

Na primjer:1 mov ax, 0C123H2 xor ax, 0E831H ; ax = 2912H

3.9.10.NOT operacija

NOT operacija je unarna operacija (tj. ona radi nad jednim operandom, ne nad dva kao binarne operacije poput AND ). Operacija NOT nad bitom je suprotna vrijednost bita, kao što kaže tablica istinitosti na Sl. 18.

X NOTX

0 11 0

Sl. 18: NOT operacija

Primjer: 1 mov ax, 0C123H2 not ax ; ax = 3EDCH

Uočava se da NOT generiše prvi komplement. Za razliku od drugih bit operacija instrukcija NOT ne mijenja sadržaj niti jednog bita u registru FLAGS.

3.9.11. TEST instrukcija

TEST instrukcija obavlja AND operaciju, ali ne smješta rezultat. Ona samo postavi FLAGS registar na bazi onog što bi rezultat trebao biti kada bi se izvršila instrukcija AND (slično kao što CMP instrukcija obavi oduzimanje, ali samo postavi FLAGS). Na primjer, ako bi rezultat bio 0, ZF bi se postavio na 1.

3.9.12.Upotreba bit operacija

Bit operacije su vrlo upotrebljive za manipulisanje pojedinačnim bitima bez modifikovanja drugih bita. Postoje tri uobičajene upotrebe ovih operacija. Za postavljanje na 1 bita n obaviti OR broja s 2n (što je binarni broj koji ima samo bit na poziciji n jednak 1)Za postavljanje na 0 bita n obaviti AND broja s binarnim brojem koji ima vrijednost 0 samo na poziciji n, a svi ostali biti imaju vrijednost 1. Ova operacija se često zove maskiranje.Za komplementiranje bita i obaviti XOR broja s 2n

Slijedi primjer koda koji implementira ove ideje.mov ax, 0C123Hor ax, 8 ; postavi na jedan bit na poziciji 3, ax = C12BHand ax, 0FFDFH ; postavi na nulu bit 5, ax = C10BHxor ax, 8000H ; invertuj bit 31, ax = 410BHor ax, 0F00H ; postavi na 1 sve u niblu, ax = 4F0BHand ax, 0FFF0H ; postavi na 0 sve u niblu, ax = 4F00Hxor ax, 0F00FH ; invertuj niblove, ax = BF0FHxor ax, 0FFFFH ; prvi komplement, ax = 40F0H

38

Page 39: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Operacija AND se takođe može koristiti da se nađe ostatak pri dijeljenju sa stepenom broja 2. Da se nađe rezultat pri dijeljenju s 2n, obaviti AND broja s maskom jednakom 2n - 1. Ova maska će sadržati jedinice od bita na poziciji 0 sve do bita na poziciji i - 1. To su upravo biti koji sadrže ostatak. Rezultat operacije AND će sačuvati te bite i postaviti na 0 ostale. Slijedi isječak koda koji traži ostatak pri dijeljenju broja 100 sa 16.

1mov eax, 100 ; 100 = 64H2mov ebx, 0000000FH ; maska = 16 - 1 = 15 or F3and ebx, eax ; ebx = ostatak = 4

Dosta se često vidi sljedeća zagonetna instrukcija u 80x86 programu:

xor eax, eax ; eax = 0

Operacija XOR nad samim sobom uvijek rezultuje nulom. Ova instrukcija se koristi jer je njen mašinski kod kraći nego odgovarajuća MOV instrukcija.

3.10.Izbjegavanje uslovnih skokova

Moderni procesori koriste veoma razvijene tehnike da izvršavaju kod što je brže moguće. Jedna uobičajena tehnika je poznata pod imenom spekulativno izvršavanje. Ova tehnika koristi paralelne mogućnosti procesiranja unutar CPUa da izvrše više instrukcija odjednom. Uslovni skokovi predstavljaju problem s ovom idejom. Procesor, generalno, ne zna da li će se skok desiti ili ne. Ako se dešava, različit skup instrukcija se izvršava, nego ako nema skoka, Procesori pokušavaju pretpostaviti da li će se desiti skok. Ako je pretpostavka pogrešna, procesor je gubio vrijeme izvršavajući pogrešan kod.

Način za rješavanje ovog problema je u izbjegavanju upotrebe uslovnih skokova kada je god to moguće. Instrukcije SETxx daju način za uklanjanje uslovnih skokova u određenim slučajevima. Ove instrukcije postavljaju vrijednost jednobajtnog registra ili memorijske lokacije na nula ili 1 na bazi stanja FLAGS registra. Slova nakon SET su ista slova korištena za uslovne skokove. Ako je odgovarajući uslov za SETxx tačan, rezultat se smjesti kao jedan, a ako je netačan upisuje se nula. Na primjer

setz al ; AL = 1 ako je ZF=1 inače AL= 0

Upotrebom ovih instrukcija mogu se razviti pametnije tehnike za proračun vrijednosti rezultata poređenja bez instrukcija grananja. Od Pentiuma II su uvedene i uslovne mov instrukcije.

3.11.Instrukcije zamjene podatakaZa konverziju između Little endian i Big Endian formata, 486 procesor uveo je novu mašinsku instrukciju BSWAP koja razmjenjuje bajtove nekog 32-bitnog registra. Na primjer,

bswap edx ; razmijeni bajtove registra edxOva instrukcija se ne može koristiti na 16-bitnim registrima. Ipak, XCHG instrukcija se može koristiti za razmjenu bajtova 16-bitnog registra koji se može rastaviti u osmobitne register, na primjer:

xchg ah,al ; razmijeni bajtove ax

Instrukcija XCHG se može koristiti i za razmjenu vrijednosti različitih registara ili registra i memorijske lokacije, npr:

xchg eax,esi ; razmijeni vrijednosti registara eax i esi

39

Page 40: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.12.Potprogrami

Potprogrami omogućavaju pravljenje modularnih programa i njihovo povezivanje s jezicima višeg nivoa, poput jezika C. Funkcije i procedure su primjeri potprograma u višim programskim jezicima.

3.12.1.Manipulacija stekom

Instrukcija PUSH ubacuje riječ ili duplu riječ na stek, oduzimajući 4 od registra ESP i zatim smješta duplu riječ na adresu [ESP]. Instrukcija POP čita duplu riječ na [ESP] i zatim dodaje 4 na ESP. Evo primjera kako ove instrukcije rade, koji pretpostavlja da je ESP inicijalno 1000H.

1push dword 1 ; 1 na adresi 0FFCh, ESP = 0FFCh2push dword 2 ; 2 na adresi 0FF8h, ESP = 0FF8h3push dword 3 ; 3 na adresi 0FF4h, ESP = 0FF4h4pop eax ; EAX = 3, ESP = 0FF8h5pop ebx ; EBX = 2, ESP = 0FFCh6pop ecx ; ECX = 1, ESP = 1000h

Stek se može koristiti kao pogodno mjesto za privremeno čuvanje podataka. Također se koristi za realizaciju poziva potprograma, prosljeđivanje parametara i lokalnih varijabli.

Na 80x86 postoji i PUSHA instrukcija koja smješta vrijednosti na stek registara EAX, EBX, ECX, EDX, ESI, EDI i EBP registri (ne u ovom redoslijedu). Instrukcija POPA se može koristiti da ih pokupi sve sa steka . Naredbe PUSH, POP, PUSHA i POPA imaju i šesnaestbitne verzije, ali se one ne koriste u tridesetdvobitnim operativnim sistemima.

3.12.2.Instrukcije CALL i RET

Procesor 80x86 ima dvije instrukcije koje koriste stek da bi realizovale potprograme brzo i jednostavno. Instukcija CALL smješta na stek adresu instrukcije neposredno iza same sebe i izvršava bezuslovni skok u potprogram. Instrukcija RET uzima adresu sa steka i skače na ovu adresu. Prilikom upotrebe ovih instrukcija važno je manipulisati s stekom korektno da bi se prava vrijednost preuzela RET instrukcijom!

Upotreba instrukcija CALL i RET omogućava gniježđenje potprograma. Neka, na primjer potprogram get_int poziva read_int. Ovaj poziv smješta na stek adresu iza instrukcije CALL read_int. Na kraju koda potprograma read_int se nalazi RET koji preuzima povratnu adresu i ponovo skače u kod od potprograma get_int. Onda, kada se izvrši instrukcija RET potprograma get_int, ona preuzima povratnu adresu pa se ponovo skače u glavni program. Ovo radi zbog LIFO (Last In First Out) svojstva steka.

Vrlo je važno pokupiti sve podatke koji su smješteni na stek. Na primjer sljedeći program se neće pravilno vratiti

1get_int:2 call read_int3 mov [ebx], eax4 push eax5 ret ; preuzima EAX vrijednost, ne povratnu adresu!!

40

Page 41: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3.12.Potprogrami

Potprogrami omogućavaju pravljenje modularnih programa i njihovo povezivanje s jezicima višeg nivoa, poput jezika C. Funkcije i procedure su primjeri potprograma u višim programskim jezicima.

3.12.1.Manipulacija stekom

Instrukcija PUSH ubacuje riječ ili duplu riječ na stek, oduzimajući 4 od registra ESP i zatim smješta duplu riječ na adresu [ESP]. Instrukcija POP čita duplu riječ na [ESP] i zatim dodaje 4 na ESP. Evo primjera kako ove instrukcije rade, koji pretpostavlja da je ESP inicijalno 1000H.

1push dword 1 ; 1 na adresi 0FFCh, ESP = 0FFCh2push dword 2 ; 2 na adresi 0FF8h, ESP = 0FF8h3push dword 3 ; 3 na adresi 0FF4h, ESP = 0FF4h4pop eax ; EAX = 3, ESP = 0FF8h5pop ebx ; EBX = 2, ESP = 0FFCh6pop ecx ; ECX = 1, ESP = 1000h

Stek se može koristiti kao pogodno mjesto za privremeno čuvanje podataka. Također se koristi za realizaciju poziva potprograma, prosljeđivanje parametara i lokalnih varijabli.

Na 80x86 postoji i PUSHA instrukcija koja smješta vrijednosti na stek registara EAX, EBX, ECX, EDX, ESI, EDI i EBP registri (ne u ovom redoslijedu). Instrukcija POPA se može koristiti da ih pokupi sve sa steka . Naredbe PUSH, POP, PUSHA i POPA imaju i šesnaestbitne verzije, ali se one ne koriste u tridesetdvobitnim operativnim sistemima.

3.12.2.Instrukcije CALL i RET

Procesor 80x86 ima dvije instrukcije koje koriste stek da bi realizovale potprograme brzo i jednostavno. Instukcija CALL smješta na stek adresu instrukcije neposredno iza same sebe i izvršava bezuslovni skok u potprogram. Instrukcija RET uzima adresu sa steka i skače na ovu adresu. Prilikom upotrebe ovih instrukcija važno je manipulisati s stekom korektno da bi se prava vrijednost preuzela RET instrukcijom!

Upotreba instrukcija CALL i RET omogućava gniježđenje potprograma. Neka, na primjer potprogram get_int poziva read_int. Ovaj poziv smješta na stek adresu iza instrukcije CALL read_int. Na kraju koda potprograma read_int se nalazi RET koji preuzima povratnu adresu i ponovo skače u kod od potprograma get_int. Onda, kada se izvrši instrukcija RET potprograma get_int, ona preuzima povratnu adresu pa se ponovo skače u glavni program. Ovo radi zbog LIFO (Last In First Out) svojstva steka.

Vrlo je važno pokupiti sve podatke koji su smješteni na stek. Na primjer sljedeći program se neće pravilno vratiti

1get_int:2 call read_int3 mov [ebx], eax4 push eax5 ret ; preuzima EAX vrijednost, ne povratnu adresu!!

40

Page 42: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4. Generisanje koda radi implementiranja programskih jezika visokog nivoa

4.1.Prevođenje standardnih kontrolnih struktura

U ovoj sekciji se vidi kako se standardne kontrolne strukture programskih jezika visokog nivoa mogu implementirati u asemblerskom jeziku.

4.1.1.If naredba

Sljedeći pseudo-kod: if (uslov)then block ; else else_block ;

se može implementirati kao:

1 ; najprije dio koda koji postavlja vrijednost registra FLAGS2 jxx else_block; odabrati xx tako da se skok desi ako je uslov netacan3 ; dio koji se izvrsava za ispunjeni uslov4 jmp endif5else_block:6 ; dio za else_block7endif:

Ako nema naredbe else, onda se grananje za else može zamijeniti grananjem za endif.1 ; najprije dio koda koji postavlja vrijednost registra FLAGS

2 jxx endif; odabrati xx tako da se skok desi ako je uslov netacan3 ; dio koji se izvrsava za ispunjeni uslov4endif:

4.1.2.While petlja

Petlja while je petlja koja se testira na početku: while ( uslov){ tijelo petlje ; }

Ovo se može prevesti u: while: ; najprije dio koda koji postavlja vrijednost registra FLAGS

jxx endwhile ; odabrati xx tako da se skok desi ako je uslov netacan ; tijelo petlje jmp whileendwhile:

4.1.3.Do while petlja

Petlja do while je petlja sa provjerom na kraju:do { tijelo petlje;} while ( uslov );

Ovo se može prevesti u:

41

Page 43: Sva predavanja na etfu iz predmeta kojeg drzi ribic

1do: ; tijelo petlje

; dio koda koji postavlja vrijednost registra FLAGS

jxx do; odabrati xx tako da se skok desi ako je uslov netacan

4.2.PotprogramiInstrukcije call i ret same po sebi nisu dovoljne da implementiraju glavne elemente potprograma koji postoje u jezicima visokog nivoa. Mora postojati način za smještanje lokalnih varijabli i parametara.

4.2.1.Konvencije poziva

Kada se pozove potprogram, dio koda koji ga poziva (pozivalac) i potprogram se moraju usaglasiti oko načina prijenosa podataka između njih. Za povezivanje programa u jeziku visokog nivoa sa asemblerskim jezikom, asemblerski jezik mora poštovati iste konvencije kao jezik visokog nivoa. Pozivne konvencije se mogu razlikovati od kompajlera do kompajlera, a mogu i varirati zavisno od toga kako se kod kompajlira (tj. da li su optimizacije uključene ili ne), ali je univerzalna konvencija je da se potprogram poziva instrukcijom CALL a iz njega se vraća instrukcijom RET.

Svi kompajleri na PC računarima za C podržavaju jednu pozivnu konvenciju koja će biti opisana u ovom poglavlju. Ove konvencije omogućavaju pravljenje potprograma u koji se može ponovo ući (reentrant). Takav potprogram se može bezbjedno pozvati iz bilo koje tačke programa, pa čak i iz samog potprograma..

4.2.2.Prosljeđivanje parametara na steku

Parameteri potprogramu se mogu proslijediti preko steka. Oni se smještaju na stek prije CALL instrukcije. Kao u jeziku C, ako se parameter mijenja od strane potprograma, mora se proslijediti adresa podatka, a ne vrijednost. Ako je veličina parametra manja od duple riječi, ona se mora konvertovati u duplu riječ, prije nego se preuzme sa steka.

Parametri na steku se skidaju u potprogramu, nego im se umjesto toga direktno pristupa na steku. Zašto?

• Pošto se oni moraju smjestiti na steku prije CALL instrukcije, povratna adresa bi se morala prvo pokupiti sa steka (i onda smjestiti na njega ponovo).

• Često se parametri koriste na više mjesta u potprogramu. Obično, oni se ne mogu čuvati u registrima tokom cijelog potprograma i morali bi biti smješteni u memoriji. Njihovo čuvanje na steku održava kopiju u memoriji kojoj se može pristupati s bilo kojeg mjesta u potprogramu.

Neka je potprogramu proslijeđen jedan parametar na steku. Kada je potprogram pozvan, stek izgleda kao na Sl. 19.

ESP + 4 ParametarESP Povratna adresa

Sl. 19 Stek prije poziva potprograma

Parametru se može pristupiti indirektnim adresiranjem ([ESP+4]). Ako se stek koristi unutar potprograma za smještanje podataka, promijeniti će se i broj koji se mora pridodati ESP registru. Na primjer, Sl. 20 prikazuje kako stek izgleda ako se dupla riječ smjesti na stek

ESP + 8 ParameterESP + 4 Povratna adresaESP Dodatno smještena riječ

Sl. 20 Stek sa dodatnim podatkom

Sada se parametar ne nalazi na adresi ESP + 4, nego je na ESP + 8. Tako je vrlo podložno greškama koristiti ESP za referenciranje parametara. Za rješavanje ovog problema, 80386 posjeduje još jedan registar, EBP. Jedina svrha ovog registra je pristup podacima na steku. Pozivna konvencija jezika C zahtijeva da

42

Page 44: Sva predavanja na etfu iz predmeta kojeg drzi ribic

potprogram prvo sačuva vrijednost registra EBP na stek i zatim postavi EBP da bude jednak registru ESP. To omogućava da se ESP mijenja kako se podaci smještaju na stek ili skidaju sa njega bez izmjene registra EBP. Na kraju potprograma, originalna vrijednost registra EBP mora biti obnovljena (zato je sačuvana na početku potprograma) Sl. 21 pokazuje opšti oblik potprograma koji prati ove konvencije.subprogram_label:

push ebp ; sacuvaj staru vrijednost registra EBP na stekmov ebp, esp ; novi EBP = ESP; potprogrampop ebp ; vratiti stari sadržaj registra EBP ret

Sl. 21: Opšti oblik potprograma

Linije 2 i 3 na Sl. 21 čine opšti početak potprograma. Linije 5 i 6 čine opšti završetak. Na Sl. 22 se vidi kako stek izgleda odmah nakon početka.

ESP+8 EBP+8 ParametarESP+4 EBP+4 Povratna adresaESP EBP Sačuvani EBP

Sl. 22: Stek nakon početka

Sada se parametru može pristupati korištenjem adrese [EBP + 8] na bilo kojoj tački potprograma bez brige šta je drugo smješteno na stek u toku potprograma.

Nakon što je potprogram završen, parametri koji su smješteni na stek moraju biti uklonjeni. C konvencija poziva specificira da program pozivalac to mora uraditi. Druge konvencije su drugačije. Na primjer, Pascal konvencija specificira da pozvani potprogram mora ukloniti parametre. Postoji i druga forma RET instrukcije koja to jednostavno radi. Neki C kompajleri podržavaju i ovu konvenciju. U deklaraciji prototipa funkcije se navede ključna riječ pascal da se kaže kompajleru da slijedi ovu konvenciju. U čemu je prednost ove konvencije? Ona je malo efikasnija od C konvencije. Zašto se ona onda ne koristi u svim C funkcijama? Generalno, C programi dopuštaju da funkcije imaju promjenjiv broj argumenata (npr., printf i scanf funkcije). Za ovaj tip funkcija, operacija koja skida parametre sa steka će se razlikovati za različite pozive funkcija. C konvencija dopušta da se ovo različito preuzimanje parametara jednostavno realizuje. Pascal konvencija čine ovu operaciju teškom. Zato Pascal konvencija (kao i Pascal jezik) ne dopušta ovaj tip funkcije. MS Windows može koristiti ovu konvenciju jer ni jedna od njegovih API funkcija (osim wvsprintf) ne uzima promjenjiv broj argumenata. Pascal konvencija je bila korištena u Windows 16 bitnim verzijama. Ipak, 32-bitne Windows funkcije koriste mješavinu C i Pascal konvencija koja se zove stdcall. Ona koristi redoslijed parametara na steku kao C konvencija, ali se stek prilagođava na kraju potprograma kao kod Pascal konvencije. Sl. 23 prikazuje kako se koristeći C konvenciju poziva može pozvati potprogram .

1push dword 1; pass 1 as parameter2call fun3add esp, 4 ; remove parameter from stack

Sl. 23: Poziv potprograma s jednim parametrom

Treća linija u ovom primjeru uklanja parametar direktnom manipulacijom stek pointera. POP instrukcija bi se mogla koristiti za ovu svrhu, ali bi zahtijevala beskoristan podatak koji se smjesti u registru. (neki kompajleri i generišu POP CX umjesto ADD ESP,4, jer je POP kraći, ali on također mijenja ECX registar)

Slijedi drugi primjer sa dva potprograma koji koriste gore objašnjenu C konvenciju poziva. On također pokazuje da se u programu može nalaziti više .data i .text segmenata u istoj datoteci s izvornim kodom. Oni

43

Page 45: Sva predavanja na etfu iz predmeta kojeg drzi ribic

će se kombinovati u jedan .data odnosno .text segment u procesu linkovanja. Razdvajanje koda i podataka u posebne segmente omogućavaju da se podaci koje potprogram koristi budu definisani blizu koda potprograma.

sub3.asm %include "asm_io.inc"

segment .datasum dd 0 segment .bssinput resd 1

;; algoritam; i = 1;; sum = 0;; while( get_int(i, &input), input != 0 ) {; sum += input;; i++;; }; print_sum(num);

segment .textglobal _asm_main

_asm_main:enter 0,0 ; startna rutinapushamov edx, 1 ; edx je ’i’ u pseudokodu

while_loop:push edx ; sacuvaj i na stekupush dword input ; sacuvaj adresu input na stekucall get_intadd esp, 8 ; ukloni i kao i &input sa stekamov eax, [input]cmp eax, 0je end_whileadd [sum], eax ; sum += inputinc edxjmp short while_loop

end_while:push dword [sum] ; stavi vrijednost sum na stekcall print_sumpop ecx ; ukloni [sum] sa stekapopaleaveret; potprogram get_int; Parametri (u redoslijedu na steku); redni broj ulaza (na [ebp + 12]); adresa rijeci gdje se smjesta ulaz (na [ebp + 8]); Napomene:; vrijednosti eax i ebx su unistene

segment .dataprompt db ") Unesi cijeli broj (0 za izlaz):",0

segment .textget_int:push ebpmov ebp, espmov eax, [ebp + 12]

44

Page 46: Sva predavanja na etfu iz predmeta kojeg drzi ribic

call print_intmov eax, promptcall print_stringcall read_intmov ebx, [ebp + 8]mov [ebx], eax ; Smjesti ulaz u memorijupop ebpret ; povratak iz potprograma

; Potprogram print_sum; stampa sumu; Parametar:; suma koja se stampa (na [ebp+8]); Napomena: brise vrijednost registra eax;

segment .dataresult db "Suma je ", 0segment .textprint_sum:

push ebpmov ebp, espmov eax, resultcall print_stringmov eax, [ebp+8]call print_intcall print_nlpop ebpret

4.2.3.Lokalne varijable na steku

Stek se može koristiti kao pogodno mjesto za lokalne varijable. Na ovom mjestu i jezik C smješta lokalne (ili automatske) varijable. Upotreba steka za varijable je važna ako se želi postići da su potprogrami ponovno pozivljivi. Ponovno pozivljiv program će raditi ako je pozvan sa bilo kog mjesta, uključujući i sam taj potprogram. Drugim riječima, ponovno pozivljivi potprogrami se mogu zvati rekurzivno. Upotreba steka za varijable također štedi memoriju. Podaci koji se ne smještaju na stek koriste memoriju od početka do kraja izvršenja cijelog programa (C naziva ove tipove varijabli globalne ili statičke ). Podaci smješteni na stek koriste memoriju samo kada je potprogram u kome su definisani aktivan. Lokalne varijable se nalaze odmah iza sačuvane EBP vrijednosti na steku. One se alociraju umanjivanjem ESP za potreban broj bajtova potrebnih za ESP u početnom dijelu potprograma. Sl. 24 prikazuje novi opšti oblik potprograma.

subprogram_label:push ebp; sacuvaj na steku staru vrijednost EBPmov ebp, esp ; novi EBP = ESPsub esp, LOCAL_BYTES ; = broj bajtova za lok. Var.; kod potprogramamov esp, ebp ; dealociraj lokalne varijablepop ebp; vrati originalni EBPret

Sl. 24: Potprogram s lokalnim varijablama

45

Page 47: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Registar EBP se koristi za pristup lokalnim varijablama. Pogledati C funkciju sa Sl. 25. Sl. 26 prikazuje kako se ekvivalentan potprogram piše u asembleru.

void calc sum ( int n , int * sump ){

int i , sum = 0;for ( i =1; i <= n; i++ )sum += i;*sump = sum;

}

Sl. 25: C verzija sum

cal_sum:push ebpmov ebp, espsub esp, 4 ; prostor za lokalnu sum

mov dword [ebp - 4], 0 ; sum = 0mov ebx, 1 ; ebx (i) = 1

for_loop:cmp ebx, [ebp+12]; da li je i <= n?jnle end_foradd [ebp-4], ebx ; sum += iinc ebxjmp short for_loop

end_for:mov ebx, [ebp+8] ; ebx = sumpmov eax, [ebp-4] ; eax = summov [ebx], eax ; *sump = sum;mov esp, ebppop ebpret

Sl. 26: Asemblerska verzija funkcije sum

Sl. 27 prikazuje kako stek izgleda nakon početka sa Sl. 26.

ESP+16 EBP+12 NESP+12 EBP+8 SumpESP+8 EBP+4 Povratna adresaESP+4 EBP Sačuvani EBPESP EBP–4 Sum

Sl. 27: Stek nakon početka

Ova sekcija steka koja sadrži parametar, povratnu adresu i lokalne varijable se zove okvir steka. Svaki poziv C funkcije kreira novi okvir steka (stack frame).

Početni i krajnji dio potprograma se mogu pojednostaviti korištenjem dvije specijalne instukcije koje su dizajnirane upravo za ovu namjenu. Instrukcija ENTER realizuje početni kod, a instrukcija LEAVE realizuje završetak. Instrukcija ENTER ima dva neposredna operanda. Za C konvenciju poziva, drugi operand je uvijek 0. Prvi operand je broj bajtova potreban za lokalne varijable. LEAVE instrukcija nema operanada. Sl.28 pokazuje kako se koriste ove instrukcije.subprogram_label:

enter LOCAL_BYTES, 0 ; = # bajtova potrebno za lokalne varijable; kod potprogramaleaveret

Sl. 28: Opšti potprogram uz upotrebu ENTER i LEAVE

46

Page 48: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Osnovni programski skelet (Sl. 10) također koristi ENTER i LEAVE. Iako instrukcije ENTER i LEAVE pojednostavljuju početak i kraj potprograma, ne koriste se previše često. Zašto? Zato jer su sporije od ekvivalentnih prostijih instrukcija. To je primjer kada se ne može pretpostaviti da je sekvenca od jedne instrukcije brža od sekvence s više instrukcija.

4.2.4.Višemodulni programi

Višemodulni program je onaj koji je sastavljen od više od jedne objektne datoteke. Svi programi predstavljeni ovdje su bili višemodulni programi. Sastojali su se od C driver objektnog programa i asemblerskog objektnog programa, (plus objektne datoteke C biblioteke). Podsjetiti se da linker kombinuje objektne datototeke u jedinstveni izvršni program. Linker mora usaglasiti reference na neku labelu u jednom modulu (tj., objektnoj datoteci) sa njenom definicijom u drugom modulu. Da bi modul A koristio labelu definisanu u modulu B, mora se koristiti extern direktiva. Ova direktiva kaže asembleru da posmatra ove labele kao eksterne za modul. Dakle, ove labele se mogu koristiti u ovom modulu, ali su definisane u drugom. Datoteka asm_io.inc definiše read_int i druge rutine kao eksterne. U asemblerskom jeziku, labelama se podrazumijevano ne pristupa eksterno. Da bi labela bila pristupačna iz drugih jezika, ona mora biti deklarirana globalna u svom modulu. Direktiva global to radi. Na osnovnom programu sa Sl. 10 vidi se da je labela asm_main definisana kao globalna. Bez ove deklaracije, bila bi greška linkera. Zašto? Jer C program ne bi mogao da nađe labelu asm_main.

Slijedi program za prethodni primjer, promijenjen da koristi dva modula. Dva potprograma (get_int i print_sum) su u odvojenim izvornim datotekama u odnosu na glavnu asm_main rutinu. asm_main.asm---------%include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1 segment .text

global _asm_main extern get_int, print_sum

_asm_main:enter 0,0 ; ulazna rutinapusha mov edx, 1 ; edx je ’i’ u pseudokodu

while_loop:push edx ; sačuvaj i na steku push dword input ; stavi adresu ulaza na stek call get_int add esp, 8 ; ukloni i i &input sa steka mov eax, [input] cmp eax, 0 je end_while add [sum], eax ; sum += inputinc edxjmp short while_loop

end_while: push dword [sum] ; stavi vrijednost sume na stek call print_sum pop ecx ; Ukloni [sum] sa steka popa leave ret

47

Page 49: Sva predavanja na etfu iz predmeta kojeg drzi ribic

--------sub4.asm:

%include "asm_io.inc" segment .dataprompt db ") Unesi cijeli broj (0 za izlaz): ", 0

segment .textglobal get_int, print_sum

get_int:enter 0,0

mov eax, [ebp + 12] call print_int mov eax, prompt call print_string call read_int mov ebx, [ebp + 8] mov [ebx], eax ; smjesti ulaz u memoriju leave ret ; vrati se u pozivaoca

segment .dataresult db "Suma je ", 0 segment .text print_sum:

enter 0,0 mov eax, result call print_string mov eax, [ebp+8] call print_int call print_nl leave ret

Prethodni primjer ima samo globalne labele s kodom, međutim globalne labele s podacima rade egzaktno na isti način.

48

Page 50: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.3.Povezivanje asemblera i C-a

Danas se vrlo malo programa piše u potpunosti u asemblerskom jeziku. Kompajleri dosta dobro prevode kod u jeziku višeg nivoa u efikasan mašinski kod. Pošto je znatno lakše pisati kod u jeziku visokog nivoa, to je popularnije. Pored toga, kod u jeziku visokog nivoa je mnogo portabilniji nego asemblerski.

Kada se asemblerski jezik koristi, to je obično za manje dijelove koda. To se radi na dva načina, pozivom asemblerskih potprograma iz C-a ili ugrađenim (inline) asemblerom. Ugrađeni asembler omogućava programeru da smjesti asemblerske naredbe direktno unutar C programa. To može biti dosta praktično, ali ima i nedostataka ugrađenog asemblera. Asemblerski kod se mora pisati u formatu koji kompajler koristi. Popularni C kompajleri ne koriste NASM format. Različiti kompajleri koriste različite formate. Borland i Microsoft koriste MASM format. MinGw GCC i Linuxov gcc zahtijevaju GAS format. GAS je asembler koji svi GNU kompajleri koriste. On koristi AT&T sintaksu koja je vrlo različita od relativno sličnih sintaksi MASMa, TASMa i NASMa. Tehnika poziva posebno prevedenih asemblerskih potprograma je daleko standardizovanija na PC računarima.

Asemblerske rutine se obično koriste sa C-om iz sljedećih razloga:

• Direktni pristup je neophodan hardverskim osobinama računara kojima je teško ili nemoguće pristupiti iz C-a.• Rutina mora biti što je moguće brža i programer može bolje optimizovati kod, nego računar.

Drugi razlog je dijelom izgubio na značaju. Tehnologija kompajlera je uznapredovala i kompajleri mogu generisati veoma efikasan kod, posebno ako su optimizacije uključene, mada još uvijek ima slučajeva kada se programom u asembleru može postići nekoliko puta veća brzina nego ekvivalentnim programom u C-u. Mane asemblerskih rutina su smanjena portabilnost i čitljivost.

4.3.1.Čuvanje registara

Prije svega C smatra da potprogram održava vrijednosti sljedećih registara: EBX, ESI, EDI, EBP, CS, DS, SS, ES. To ne znači da potprogram njih ne može interno mijenjati. To zapravo znači da ako ih potprogram mijenja, on mora obnoviti njihove vrijednosti prije povratka iz potprograma. Vrijednosti registara EBX, ESI i EDI se ne smiju mijenjati jer C koristi ove registre za registarske varijable. Obično se stek koristi za čuvanje originalnih vrijednosti ovih registara. Ključna riječ register se može koristiti u deklaraciji C-ovske varijable da se kaže kompajleru da koristi registar za ovu varijablu umjesto memorijske lokacije. To se zove registarska varijabla. Moderni kompajleri ovo rade automatski, bez potrebe za nekim sugestijama.

4.3.2.Oznake funkcija

Većina C kompajlera pridoda znak podvlačilice (_) na početak imena funkcija I globalnih i statičkih varijabli. Na primjer, funkciji koja se zove f bit će pridružena labela _f. Tako, ako ona treba da bude asemblerska rutina, mora se imenovati _f, a ne f. Linuxov gcc kompajler ne pridodaje ovaj znak. Pod Linuxovim ELF izvršnim programima, prosto bi se koristila labela f za C funkciju f. Ipak, gcc za Win32 dodaje podvlačilicu. Primijetiti da u asemblerskom skelet programu (Sl. 10), labela za glavnu rutinu je asm_main.

4.3.3.Redoslijed prosljeđivanje parametaraPod C konvencijom poziva, argumenti funkcije se smještaju na stek u obrnutom redoslijedu od onog u kome se pojavljuju u pozivu funkcije. Pogledati sljedeću naredbu u C-u: printf("x = %d",x); Sl. 29 pokazuje kako bi ovo bilo prevedeno (prikazano u ekvivalentnom NASM formatu). segment .data x dd 0 format db "x = %d", 0segment .text

49

Page 51: Sva predavanja na etfu iz predmeta kojeg drzi ribic

push dword [x] ; na stek vrijednost xpush dword format ; na stek adresu format stringacall _printf ; primjetiti podvlacilicu!add esp, 8 ; ukloniti parametre sa steka

Sl. 29: Poziv printf

Sl. 30 prikazuje kako stek izgleda nakon uvodnog dijela printf funkcije.

EBP + 12 vrijednost xEBP + 8 adresa formatnog stringaEBP + 4 Povratna adresaEBP sačuvan EBP

Sl. 30: Stek unutar printf

Funkcija printf je jedna od onih iz C biblioteke koje mogu da imaju proizvoljan broj argumenata. Pravila C pozivnih konvencija su specijalno pisana da omoguće ovaj tip funkcija. Pošto se adresa format stringa posljednja smješta na stek, njena lokacija na steku će uvijek biti na EBP + 8 bez obzira koliko se parametara prosljeđuje funkciji. Funkcija printf onda gleda u formatni string da odredi koliko se parametara treba proslijediti I traži ih na steku. Naravno, ako se napravi greška printf("x = %d"), naredba printf će i dalje štampati duplu riječ na adresi [EBP + 12]. Ipak, to neće biti vrijednost varijable x!

4.3.4.Računanje adrese lokalnih varijabli

Traženje adrese labele definisane u data ili bss segmentu je jednostavno. U osnovi, linker to radi. Ipak, računanje adrese lokalne varijable (ili parametra) nije tako prosto. Ipak, to je vrlo česta potreba prilikom poziva potprograma. Na primjer, kada se prosljeđuje adresa varijable ( imena x) funkciji (imena pp). Ako se x nalazi na EBP - 8 na steku, ne može se prosto koristiti:

mov eax, ebp - 8

Zašto? Vrijednost koju MOV smješta u EAX mora biti izračunata od strane asemblera dakle u konačnici mora biti konstanta. Ipak, postoji instrukcija koja obavlja željeni račun. Zove se LEA (Load Effective Address ). Sljedeći kod računa adresu varijable x I smješta je u EAX:

lea eax, [ebp - 8]

Sada EAX sadrži adresu varijable x i može se smjestiti na stek prilikom poziva funkcije pp. Da se izbjegnu zabune, ovo izgleda kao da se čitaju podaci sa adrese [EBP-8]; ali to nije slučaj. Instrukcija LEA nikada ne čita memoriju. Ona samo računa adresu lokacije koja bi bila pročitana nekom drugom instrukcijom i smješta tu adresu u prvi registarski operand. Pošto ona ne čita memoriju, nema potrebe (niti je dopušteno) za navođenjem veličine operanda (npr dword).

4.3.5.Povratne vrijednosti

C funkcije koje nisu tipa void vraćaju vrijednost. C specifikacije poziva navode kako se to radi. Povratne adrese se uvijek prosljeđuju preko registara. Svi integralni tipovi (char, int, enum, itd.) se vraćaju u EAX registru. Ako su podaci manji od 32-bita, proširuju se na 32-bita kada se smještaju u EAX. (Kako se proširuju, zavisi da li su predznačeni ili nepredznačeni tipovi.) 64-bitne vrijednosti se vraćaju u EDX_EAX registarskom paru. Pointerske vrijednosti se također smještaju u EAX. Realni brojevi se smještaju u ST0 registar matematičkog koprocesora. (Ovaj registar će biti spomenut u poglavlju o pokretnom zarezu.)

4.3.6.Druge konvencije poziva

Gornja pravila opisuju C konvenciju poziva koju podržavaju svi 80x86 C kompajleri. Često kompajleri podržavaju i druge konvencije poziva. Kada se povezuje s asemblerskim jezikom, vrlo je važno znati koju konvenciju poziva kompajler koristi prilikom poziva korisnikove funkcije. Obično se podrazumijeva standardna konvencija, ipak to nije uvijek slučaj. Kompajleri koji koriste više konvencija često imaju

50

Page 52: Sva predavanja na etfu iz predmeta kojeg drzi ribic

komandne opcije koje se koriste za promjenu podrazumijevane konvencije. One također daju proširenja C sintakse za eksplicitno dodjeljivanje pozivnih konvencija individualnim funkcijama. Ipak, ova proširenja nisu standardizovana i mogu varirati od jednog kompajlera do drugog.

GCC kompajler omogućava različite konvencije poziva. Konvencija funkcije se može eksplicitno deklarirati upotrebom proširenja atributa. Na primjer, za deklarisanje void funkcije koja koristi standardnu konvenciju poziva koja se zove f, i koristi jedan cjelobrojni parametar, koristi se sljedeća sintaksa za njen prototip:

void __cdecl f ( int);

GCC također podržava konvenciju standardnog poziva. Gornja funkcija se može deklarisati da koristi ovu konvenciju zamjenom cdecl sa stdcall. Razlika između stdcall i cdecl je što stdcall zahtijeva da potprogram ukloni parametre sa steka, (kao što Pascal konvencija poziva radi). Tako se konvencija stdcall može koristiti samo sa funkcijama koje uzimaju fiksan broj argumenata (dakle ne one poput printf i scanf).

Borland i Microsoft koriste zajedničku sintaksu da deklarišu konvencije poziva. Oni dodaju ključne riječi cdecl i stdcall u C. Ove ključne riječi djeluju kao modifikatori funkcije i pojavljuju se neposredno prije imena funkcije u prototipu. Na primjer, gore definisana funkcija f bi se na kompajlerima firmi Borland i Microsoft deklarirala ovako:

void cdecl f ( int );

Ima prednosti i mana svake od pozivnih konvencija. Glavna prednost cdecl konvencije je što je jednostavna i veoma fleksibilna. Može se koristiti za bilo koju vrstu C funkcije i C kompajlera. Upotreba drugih konvencija može ograničiti portabilnost potprograma. Njena glavna mana je što može biti sporija od nekih drugih i koristi više memorije (pošto svaki poziv funkcije zahtijeva uklanjanje parametara sa steka)

Prednost stdcall konvencije je što koristi manje memorije nego cdecl. Nema potrebe za čišćenjem steka nakon CALL instrukcije. Glavna joj je mana što se ne može koristiti sa funkcijama koje imaju promjenjiv broj argumenata.

Prednost upotrebe konvencije koja koristi registre za prosljeđivanje cjelobrojnih parametara je brzina. Mana je što je ta konvencija složenija. Neki parametri su u registrima, a neki na steku.

Slijedi primjer koji pokazuje kako se asemblerska rutina može povezati s C programom. (Primjetiti da ovaj program ne koristi asemblerski okvir (Sl. 10) niti driver.c modul.)

sub5.asm;potprogram _calc_sum;Traži sumu cijelih brojeva od 1 do n;Parametri:; n -dokle sumirati(na [ebp + 8]); sump - pointer na int da smjesti sumu (na [ebp +12]);pseudo C kod:;void calc_sum( int n, int * sump );{; int i, sum = 0;; for( i=1; i <= n; i++ ); sum += i;; *sump = sum;; }

segment .text global _calc_sum

;; local variable:; sum at [ebp-4]

_calc_sum:enter 4,0 ; smjesti prostor za sumu na steku

51

Page 53: Sva predavanja na etfu iz predmeta kojeg drzi ribic

push ebx ; VAZNO!mov dword [ebp-4],0 ; sum = 0dump_stack 1, 2, 4 ;;print out stack from ebp-8 to ebp+16mov ecx, 1 ; u pseudokodu ecx je i

for_loop:cmp ecx, [ebp+8] ; poredi i sa njnle end_for; ako nije i <= n, izađiadd [ebp-4], ecx ; sum += iinc ecxjmp short for_loop

end_for:mov ebx, [ebp+12]; ebx = sumpmov eax, [ebp-4] ; eax = summov [ebx], eaxpop ebx ; vrati ebxleaveret

main5.c#include <stdio.h>/* prototip za asemblersku rutinu */void calc sum ( int , int * ) attribute ((cdecl ));int main( void ){

int n , sum;printf ("Suma cijelih brojeva do : " );scanf („%d”, &n);calc_sum(n, &sum);printf (”Suma je %d”, sum);return 0;

}

Zašto je bitna linija „PUSH EBX“ programa sub5.asm? Zato što C konvencija poziva zahtijeva da vrijednost registra EBX bude neizmijenjena u funkcijskom pozivu. Ako to nije urađeno, vrlo je vjerovatno da program neće korektno raditi.

Linija 25 demonstrira kako radi makro za štampanje steka. Podsjetiti se da je prvi parametar numerička oznaka, a drugi i treći određuju koliko duplih riječi treba odštampati ispod i iznad registra EBP respektivno. Sl. 31 prikazuje primjer izvršenja programa.

Suma cijelih brojeva do: 10Stack Dump # 1

EBP = BFFFFB70 ESP = BFFFFB68+16 BFFFFB80 080499EC+12 BFFFFB7C BFFFFB80+8 BFFFFB78 0000000A+4 BFFFFB74 08048501+0 BFFFFB70 BFFFFB88-4 BFFFFB6C 00000000-8 BFFFFB68 4010648C

Suma je 55

Sl. 31: Primjer izvršenja sub5 programa

52

Page 54: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Iz ovog prikaza se može vidjeti da je adresa duple riječi gdje se smješta suma BFFFFB80 (na EBP + 12); broj do kojeg se sumira je 0000000A (na EBP + 8); povratna adresa za rutinu je 08048501 (na EBP + 4); sačuvana vrijednost EBP registra BFFFFB88 (na EBP); vrijednost lokalne varijable je 0 na (EBP - 4); i konačno, sačuvana EBX vrijednost je 4010648C (at EBP - 8).

Funkcija calc_sum se može prepisati da vrati sumu kao svoju povratnu vrijednost, umjesto korištenja pointerskog parametra. Pošto je suma cjelobrojna vrijednost, ona se može ostaviti u EAX registru. Jedanaesta linija programa main5.c bi se izmijenila u:

sum = calc_sum(n);

Također, prototip funkcije calc_sum se treba promijeniti. Ispod se nalazi modifikovati asemblerski program.

sub6.asm; potprogram _calc_sum; trazi sumu cijelih brojeva od 1 do n; Parametri:; n -do koliko sumirati (na [ebp + 8]); Povratna vrijednost:; vrijednost sume; pseudo C program:;int calc_sum( int n );{; int i, sum = 0;; for( i=1; i <= n; i++ ); sum += i;; return sum;;}

segment .textglobal _calc_sum;; lokalne varijable:; sum na [ebp-4]

calc_sum:

enter 4,0 ; Prostor za sum na steku mov dword [ebp-4],0 ; sum = 0 mov ecx, 1 ; ecx u pseudokodu je varijabla i

for_loop: cmp ecx, [ebp+8] ; poredi i sa n jnle end_for ; ako nije i <= n, izadji add [ebp-4], ecx ; sum += i inc ecx jmp short for_loop

end_for: mov eax, [ebp-4] ; eax = sum leave ret

53

Page 55: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.3.7.Poziv C funkcija iz asemblerskog programa

Velika prednost povezivanja jezika C i asemblerskog jezika je što je dopušteno asemblerskom kodu da pristupa velikoj C biblioteci i korisničkim funkcijama. Na primjer, ako neko želi pozvati funkciju scanf da pročita cijeli broj sa tastature, Sl. 32 prikazuje program koji ovo radi.

segment .dataformat db "%d", 0

segment .textlea eax, [ebp-16]push eaxpush dword formatcall _scanfadd esp, 8

Sl. 32: Poziv scanf iz asemblerskog jezika

Važno je napomenuti da scanf prati C pozivne standarde. To znači da on čuva vrijednost registara EBX, ESI i EDI ali da će EAX, ECX i EDX registri vjerovatno biti promijenjeni. Zapravo, EAX će definitivno biti promijenjen jer on sadrži povratnu vrijednost scanf poziva. Za druge primjere povezivanja s jezikom C, pogledati kod unutar asm_io.asm koji je bio korišten da se kreira asm_io.obj.

4.3.8.Ponovno pozivljivi i rekurzivni potprogrami

Ponovno pozivljiv program mora zadovoljavati sljedeće kriterije:

• Ne smije modificirati instrukcije programa. U višim programskim jezicima to je teško, ali u asembleru nije teško programu da modifikuje vlastiti kod. Na primjer: mov word [cs:$+7], 5; kopiraj 5 u riječ 7 bajtova naprijedadd ax, 2; prethodna naredba je promijenila 2 u 5!

Ovaj program bi mogao raditi u realnom režimu rada, ali u zaštićenom režimu segment koda je označen kao samo za čitanje. Kad se prva linija izvrši, program će biti prekinut na ovim sistemima. Ovaj način programiranja je loš iz više razloga. Konfuzan je, težak za održavanje i ne dopušta dijeljenje koda (vidi dolje).

• Ne smije modifikovati globalne podatke (poput onih u data i bss segmentima). Sve varijable se smještaju na steku. Pisanje ponovno pozivljivih programa ima prednosti. • Ponovno pozivljiv potprogram se može rekurzivno pozvati.

• Ponovno pozivljiv program se može dijeliti između više procesa. Na mnogim višezadaćnim sistemima, ako ima više instanci programa koje se izvršavaju, samo jedna kopija koda je u memoriji. Dijeljene biblioteke i DLLovi (Dynamic Link Libraries ) koriste ovu ideju.

• Ponovno pozivljiv potprogram mnogo bolje radi u višenitnim programima. Windows 9x/NT i većina UNIX kompatibilnih operativnih sistema (Solaris, Linux, itd.) podržavaju višenitno programiranje (unutar jednog programa ima više istovremenih zadataka)

4.3.9. Rekurzivni potprogrami

Ovaj tip potprograma poziva samog sebe. Rekurzija može biti direktna ili indirektna. Direktna rekurzija se događa kada se potprogram, npr. iznos, poziva unutar tijela potprograma iznos. Indirektna rekurzija se dešava kada potprogram ne zove sebe direktno, nego preko drugog potprograma kojeg poziva. Na primjer, potprogram iznos pozove potprogram saberi, a potprogram saberi pozove potprogram iznos.

54

Page 56: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Rekurzivni potprogrami moraju imati uslov završetka. Kada je ovaj uslov ispunjen, više se ne vrše rekurzivni pozivi. Ako rekurzije nemaju uslov završetka, ili taj uslov nikad nije ispunjen, rekurzija nikada neće prestati (poput je beskonačne petlje). Sl. 33. prikazuje funkciju koja rekurzivno računa faktorijel.

1 ; trazi n!2 segment .text3 global _fact4_fact:5 enter 0,06

7 mov eax, [ebp+8] ; eax = n8 cmp eax, 19 jbe term_cond ; ako je n <= 1, zavrsetak10 dec eax11 push eax12 call _fact ; eax = fact(n-1)13 pop ecx ; brise parametar s steka14 mul dword [ebp+8]; edx:eax = eax * [ebp+8]15 jmp short end_fact16term_cond:17 mov eax, 118end_fact:19 leave20 ret

Sl. 33: Rekurzivni faktorijel

Može se pozvati iz C-a pomoću: x = fact (3); /* trazi 3! */

55

Page 57: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.3.10.Tipovi varijabli u C-u

C nudi više tipova varijabli:

global Ove varijable se nalaze izvan bilo koje funkcije i smještene su na fiksne memorijske lokacije (u data ili bss segmentima) i postoje od početka programa do njegovog kraja. Podrazumijeva se da im je moguće pristupiti iz bilo koje funkcije programa, ali ako su deklarirane kao statične, samo funkcije iz istog modula im mogu pristupati. (u asemblerskoj terminologiji labela je interna, a ne eksterna)

static Ovo su lokalne varijable funkcije koje su deklarirane kao static. (Nažalost, C koristi ključnu riječ static za dvije različite namjene!). Ove varijable su takođe smještene na fiksnim memorijskim lokacijama (u data ili bss), ali im se može direktno pristupiti samo u funkciji u kojoj su definisane.

automatic Ovo je podrazumijevani tip za C varijable definisane unutar funkcije. Ove varijable se alociraju na steku kada se pozove funkcija gdje su definisane i dealociraju kada se iz funkcije vraća. Tako, one nemaju fiksne memorijske lokacije.

register Ova ključna riječ zahtijeva od kompajlera da koristi registar za podatke koji se čuvaju u ovoj varijabli. To je samo zahtjev, koji kompajler ne mora ispuniti. Ako se adresa varijable koristi igdje u programu, zahtjev neće biti ispunjen, jer registri nemaju adresa. Također, samo prosti integralni tipovi mogu biti registarske vrijednosti. C kompajleri često automatski pretvaraju normalne varijable u registarske varijable bez savjeta programera.

volatile Ova ključna riječ kaže kompajleru da se vrijednost varijable može promijeniti u bilo kojem trenutku. To znači da kompajler ne smije praviti nikakve pretpostavke kada se varijabla mijenja. Često kompajler može smjestiti vrijednost varijable privremeno u registar i koristiti je umjesto varijable u dijelu koda. Ovakve optimizacije se ne smiju praviti sa volatile varijablama. Tipičan primjer volatile varijabli bi bile one koje se mogu mijenjati s dvije niti višenitnih programa. Na primjer:

1 x = 10;2 y = 20;3 z = x;

Ako bi x izmijenila druga nit, moguće je da se ta izmjena desi između prve i treće linije, pa tada z ne bi bilo 10. Ipak, ako x nije deklarisan kao volatile, kompajler bi mogao pretpostaviti da je x neizmijenjen i postaviti z na 10.

Druga primjena volatile je da kompajler izbjegava upotrebu registara za varijable.

4.4.Nizovi

Niz je kontinualan blok liste podataka u memoriji. Svaki element niza mora biti istog tipa i koristiti doslovce isti broj bajtova u memoriji za smještanje. Zbog ove osobine nizovi omogućavaju efikasan pristup podacima na bazi pozicije u nizu, odnosno indeksa. Adresa bilo kojeg elementa se može izračunati na bazi tri podatka:

• Adresa prvog elementa niza.• Broj bajtova svakog elementa • Indeks elementa

Zgodno je da indeks prvog elementa niza nula. (kao u C). Moguće je koristiti druge vrijednosti za prvi indeks, ali to komplikuje račun.

4.4.1.Definisanje nizova

56

Page 58: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.3.10.Tipovi varijabli u C-u

C nudi više tipova varijabli:

global Ove varijable se nalaze izvan bilo koje funkcije i smještene su na fiksne memorijske lokacije (u data ili bss segmentima) i postoje od početka programa do njegovog kraja. Podrazumijeva se da im je moguće pristupiti iz bilo koje funkcije programa, ali ako su deklarirane kao statične, samo funkcije iz istog modula im mogu pristupati. (u asemblerskoj terminologiji labela je interna, a ne eksterna)

static Ovo su lokalne varijable funkcije koje su deklarirane kao static. (Nažalost, C koristi ključnu riječ static za dvije različite namjene!). Ove varijable su takođe smještene na fiksnim memorijskim lokacijama (u data ili bss), ali im se može direktno pristupiti samo u funkciji u kojoj su definisane.

automatic Ovo je podrazumijevani tip za C varijable definisane unutar funkcije. Ove varijable se alociraju na steku kada se pozove funkcija gdje su definisane i dealociraju kada se iz funkcije vraća. Tako, one nemaju fiksne memorijske lokacije.

register Ova ključna riječ zahtijeva od kompajlera da koristi registar za podatke koji se čuvaju u ovoj varijabli. To je samo zahtjev, koji kompajler ne mora ispuniti. Ako se adresa varijable koristi igdje u programu, zahtjev neće biti ispunjen, jer registri nemaju adresa. Također, samo prosti integralni tipovi mogu biti registarske vrijednosti. C kompajleri često automatski pretvaraju normalne varijable u registarske varijable bez savjeta programera.

volatile Ova ključna riječ kaže kompajleru da se vrijednost varijable može promijeniti u bilo kojem trenutku. To znači da kompajler ne smije praviti nikakve pretpostavke kada se varijabla mijenja. Često kompajler može smjestiti vrijednost varijable privremeno u registar i koristiti je umjesto varijable u dijelu koda. Ovakve optimizacije se ne smiju praviti sa volatile varijablama. Tipičan primjer volatile varijabli bi bile one koje se mogu mijenjati s dvije niti višenitnih programa. Na primjer:

1 x = 10;2 y = 20;3 z = x;

Ako bi x izmijenila druga nit, moguće je da se ta izmjena desi između prve i treće linije, pa tada z ne bi bilo 10. Ipak, ako x nije deklarisan kao volatile, kompajler bi mogao pretpostaviti da je x neizmijenjen i postaviti z na 10.

Druga primjena volatile je da kompajler izbjegava upotrebu registara za varijable.

4.4.Nizovi

Niz je kontinualan blok liste podataka u memoriji. Svaki element niza mora biti istog tipa i koristiti doslovce isti broj bajtova u memoriji za smještanje. Zbog ove osobine nizovi omogućavaju efikasan pristup podacima na bazi pozicije u nizu, odnosno indeksa. Adresa bilo kojeg elementa se može izračunati na bazi tri podatka:

• Adresa prvog elementa niza.• Broj bajtova svakog elementa • Indeks elementa

Zgodno je da indeks prvog elementa niza nula. (kao u C). Moguće je koristiti druge vrijednosti za prvi indeks, ali to komplikuje račun.

4.4.1.Definisanje nizova

56

Page 59: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Da se definiše inicijalizirani niz u data segmentu mogu se koristiti normalne db, dw, itd. direktive. NASM također pruža korisnu direktivu TIMES koja se koristi da se ponovi naredba više puta bez potrebe da se naredba ručno duplicira. Sl. 34 prikazuje više primjera ovoga.

Za definiciju neinicijaliziranih nizova u bss segmentu, koristiti direktive resb, resw, itd. Ove direktive imaju operand koji specificira koliko jedinica memorije trebaju da rezervišu. Sl. 34 također prikazuje primjere ove vrste definicija.

1segment .data2; definiše niz 10 duplih riječi inicijaliziran na 1,2,..,103a1 dd 1,2,3,4,5,6,7,8,9,104; definiše niz od 10 riječi inicijaliziranih na 05a2 dw 0,0,0,0,0,0,0,0,0,06; isto kao prethodno, koristeći TIMES7a3 times 10 dw 08; definiše niz bajtova od 200 nula i onda od 100 jedinica9a4 times 200 db 01 times 100 db 11

1segment .bss1; definiše niz od 10 neinicijaliziranih duplih riječi1a5 resd 101; definiše niz od 100 neinicijaliziranih riječi1a6 resw 100

Sl. 34: Definisanje nizova

4.4.2.Definisanje nizova kao lokalne varijable na steku

Nema direktnog načina za definisanje niza lokalnih varijabli na steku. Kao i ranije, računa se ukupan broj bajtova potreban za sve lokalne varijable uključujući nizove i oduzima se od ESP (direktno ili koristeći ENTER instrukciju). Na primjer, ako je funkciji potrebna char varijabla, dvije duple riječi i element niza od 50 elemenata, bilo bi potrebno 1+ 2 × 4 + 50 × 2 = 109 bajtova. Ipak, broj koji se oduzima od ESP treba biti umnožak broja 4 (112 u ovom slučaju) da održi ESP na adresi koja je umnožak dužine duple riječi. Varijable unutar tih 109 bajtova se mogu rasporediti na više načina. Sl. 35 prikazuje jedan mogući način. Neiskorišteni dio je ovdje da zadrži duple riječi na adresi koja je umnožak dužine duple riječi rad ubrzanja memorijskog pristupa.

EBP – 1 char

EBP-2 Neiskorišteno

EBP-4 dword 1EBP - 8 dword 2EBP - 12 Word array, element 0EBP - 14 Word array, element 1….EBP-100 Word array, element 49

Sl. 35: Raspoređivanje na steku

4.4.3.Pristup elementima niza

U asemblerskom jeziku nema [ ] operatora kakav postoji u C. Da bi se pristupilo elementu niza njegova adresa se mora izračunati. Usporediti sljedeće dvije definicije niza:

array1 db 5,4,3,2,1 ; array of bytesarray2 dw 5,4,3,2,1 ; array of words

Evo nekih primjera korištenja ovih nizova: 1mov al, [array1] ; al = array1[0]2mov al, [array1 + 1] ; al = array1[1]

57

Page 60: Sva predavanja na etfu iz predmeta kojeg drzi ribic

3mov [array1 + 3], al ; array1[3] = al4mov ax, [array2] ; ax = array2[0]5mov ax, [array2 + 2] ; ax = array2[1] (NE array2[2]!)6mov [array2 + 6], ax ; array2[3] = ax7mov ax, [array2 + 1] ; ax = ??

U liniji 5, element 1 niza riječi se referencira, a ne element 2. Zašto? Riječi su dvobajtne jedinice, pa za kretanje ka sljedećem elementu niza riječi mora se pomjeriti za dva bajta naprijed, a ne za jedan. Linija 7 će pročitati jedan bajt prvog elementa i jedan bajt drugog. U jeziku C, kompajler posmatra tip pointera u određivanju koliko bajtova se pomjeriti u izrazu koji koristi pokazivačku aritmetiku, tako da programer to ne mora raditi. Međutim, u asembleru programer mora uzeti u obzir veličinu elemenata niza prilikom kretanja kroz elemente niza.

Sl. 36 prikazuje dio koda koji sabira sve elemente niza array1 iz prethodnog primjera.1 mov ebx, array1 ; ebx = adresa array12 mov dx, 0 ; dx sadrži sumu3 mov ah, 0 ; ?4 mov ecx, 55 lp:6 mov al, [ebx] ; al = *ebx7 add dx, ax ; dx += ax (ne al!)8 inc ebx ; bx++9 loop lp

Sl. 36: Sumiranje elemenata niza (Verzija 1)

U liniji 7, AX se sabira sa DX. Zašto ne AL? Prvo, oba operanda ADD instrukcije moraju biti iste veličine. Drugo, bilo bi lako sabrati bajtove i dobiti sumu koja je prevelika da stane u jedan bajt. Upotrebom registra DX, sume do 65,535 su dopuštene. Ipak, treba uočiti da se AH također dodaje. Zato je AH postavljen na nulu u liniji 3. Postavljanje AH na nulu to zero implicitno pretpostavlja da je AL nepredznačeni broj. Ako je predznačeni, odgovarajuća akcija bi bila umetanje instrukcije CBW između linija 6 i 7).

Sl. 37 i Sl. 38 prikazuju dva alternativna načina za račun sume. 1 mov ebx, array1 ; ebx = adresa array12 mov dx, 0 ; dx će držati sumu3 mov ecx, 54 lp:5 add dl, [ebx] ; dl += *ebx6 jnc next ; ako nije carry idi na next7 inc dh ; inc dh8next:9 inc ebx ; ebx++10 loop lp

Sl. 37: Sumiranje elemenata niza (Verzija 2)

1 mov ebx, array1 ; ebx = adresa array12 mov dx, 0 ; dx sadrži sum3 mov ecx, 54 lp:5 add dl, [ebx] ; dl += *ebx6 adc dh, 0 ; dh += carry flag + 07 inc ebx ; bx++8 loop lp

Sl. 38: Sumiranje elemenata niza (Verzija 3)

58

Page 61: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.4.4.Druge primjene instrukcije LEA

LEA instrukcija se može koristiti i za druge namjene pored prostog računa adrese. Dosta česta namjena je za brze aritmetičke operacije. Na primjer:

lea ebx, [4*eax + eax]

Ovo efektivno smješta vrijednost 5 × EAX u EBX. Upotreba LEA za ovo je jednostavnije i brže nego upotreba naredbe MUL. Ipak, treba paziti da izrazi unutar ugaonih zagrada predstavljaju legalnu indirektnu adresu. Tako, na primjer, ova instrukcija se ne može koristiti za množenje sa 6 jednom instrukcijom.

4.4.5.Višedimenzionalni nizovi

Višedimenzionalni nizovi nisu mnogo različiti od jednodimenzionalnih nizova, već razmatranih. Zapravo, oni su predstavljeni u memoriji upravo tako, kao i jednodimenzionalni nizovi.

4.4.6.Dvodimenzionalni nizovi

Naravno, najprostiji višedimenzionalni niz je dvodimenzionalni. Dvodimenzionalni niz se često predstavlja kao tablica elemenata. Svaki element je identificiran parom indeksa. Po konvenciji, prvi indeks predstavlja indeks reda a drugi indeks kolone.

Na primjer, niz koji ima tri reda i dvije kolone se definiše kao: int a [3][2];

C kompajler bi rezervisao prostor za 6 (= 2 × 3) cijelih brojeva i mapirao elemente na sljedeći način:

Indeks 0 1 2 3 4 5Element a[0][0] a[0][1] a[1][0] a[1][1] a[2][0] a[2][1]

Ova tablica pokazuje da se element a[0][0] smješta na početak jednodimenzionalnog niza od 6 elementa. Element a[0][1] se smješta na sljedeću poziciju (indeks 1) itd. Svaki red dvodimenzionalnog niza se smješta kontinualno u memoriju. Iza posljednjeg elementa u redu slijedi prvi element narednog reda. To se zove raspoređivanje po redovima. U ovom obliku C/C++ kompajleri predstavljaju nizove. Kako kompajler određuje gdje se a[i][j] nalazi u raspoređivanju po redovima? Prosta formula će izračunati indeks iz i,j. Formula u ovom slučaju je 2i + j . Nije teško zaključiti kako je ova formula dobivena. Svaki red ima dva elementa, pa je početni element i-og reda na poziciji 2i. Nakon toga se pridoda kozicija kolone j, sabiranjem j s 2i.

Ova analiza pokazuje kako se formula može generalizovati za niz od N kolona: N ×i +j . Ova formula ne ovisi od broja redova.

Evo primjera kako gcc kompajler prevodi ovaj kod, koristeći gore definisani niz:

x = a[i ][ j ];

Sl. 39 prikazuje u asemblerskom obliku ovaj prijevod.1mov eax, [ebp - 44] ; ebp - 44 je lokacija i2sal eax, 1 ; pomoži i s 23add eax, [ebp - 48] ; dodaj j4mov eax, [ebp + 4*eax - 40]; ebp - 40 je adresa a[0][0]5mov [ebp - 52], eax ; smjesti rezultat u x (na ebp - 52)

Sl. 39: Asemblerski kod za x = a[i ][ j ]

59

Page 62: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Tako, kompajler suštinski prevodi instrukciju u:

x = *(&a[0][0] + 2*i + j );

i zapravo bi i programer mogao pisati na isti način.

Nema posebnog razloga zašto je izabrana reprezentacija po redovima. Mogla bi raditi i reprezentacija po kolonama. Index 0 1 2 3 4 5Element a[0][0] a[1][1] a[2][0] a[0][1] a[1][1] a[2][1]

U reprezentaciji po kolonama, svaka kolona je smještena kontinualno. Element [i][j] se nalazi na poziciji i + 3j . Neki jezici (FORTRAN, na primjer) koriste reprezentaciju po kolonama. To je bitno prilikom povezivanja koda u različitim jezicima.

4.4.7.Dimenzije iznad dva

Za dimenzije iznad 2, primjenjuje se ista osnovna ideja. Neka je dat trodimenzionalni niz:

int b [4][3][2];

Ovaj niz bi se smjestio kao da su u pitanju četiri dvodimenzionalna niza, svaki dimenzija [3][2] redom u memoriji. Donja tabela pokazuje kako to u memoriji izgleda:

Indeks 0 1 2 3 4 5Element b[0][0][0] b[0][0][1] b[0][1][0] b[0][1][1] b[0][2][0] b[0][2][1]

Indeks 6 7 8 9 10 11Element b[1][0][0] b[1][0][1] b[1][1][0] b[1][1][1] b[1][2][0] b[1][2][1]

Formula za račun pozicije elementa b[i][j][k] je 6i + 2j + k. Element 6i je određen veličinom nizova [3][2]. Generalizirano, za niz dimenzioniran kao a[L][M][N] pozicija elementa a[i][j][k] bi bila M ×N ×i +N ×j + k. Ponovo, prva dimenzija (L) se ne javlja u formuli.

Za veće dimenzije, isti proces se generalizira. Za n-dimenzionalni niz dimenzija D1 do Dn, pozicija elementa određena indeksima i1 do in se određuje formulom:

D2 × D3 · · · × Dn × i1 + D3 × D4 · · · × Dn × i2 + · · · + Dn × in-1 + in

Prva dimenzija D1, se ne javlja u formuli.Za predstavljanje po kolonama, opšta formula bi bila: i1 + D1 × i2 + · · · + D1 × D2 × · · · × Dn-2 × in-1 + D1 × D2 × · · · × Dn-1 × in

U ovom slučaju, posljednja dimenzija Dn , se ne pojavljuje u formulama.

4.4.8.Prosljeđivanje višedimenzionalnih nizova kao parametara u C

Reprezentacija višedimenzionalnih nizova po redovima ima direktan efekat na programiranje u C-u. Za jednodimenzionalne nizove, veličina niza nije potrebna da bi se izračunalo gdje se neki specifični element nalazi u memoriji. To nije slučaj s višedimenzionalnim nizovima. Za pristup elementima ovih nizova, kompajler mora znati sve osim prve dimenzije. To postaje uočljivo kada se pravi prototip funkcije koja koristi višedimenzionalni niz kao parametar. Sljedeći primjer se neće prevesti:

void f ( int a [ ][ ] ); /* nema informacije o dimenziji */

ali sljedeći hoće: void f ( int a [ ][2] );

Bilo koji dvodimenzionalni niz s dvije kolone se može proslijediti ovoj funkciji. Prva dimenzija nije neophodna. Ne miješati ovo s funkcijom s ovim prototipom:

60

Page 63: Sva predavanja na etfu iz predmeta kojeg drzi ribic

void f ( int * a [ ] );

Ovo definiše jednodimenzionalni niz cjelobrojnih pointera (koji se može koristiti za kreiranje niza nizova ponašanja sličnog dvodimenzionalnim nizovima).

Za višedimenzionalne nizove, sve dimenzije, osim prve, niza se moraju navesti za parametre. Na primjer, parametar koji je četvorodimenzionalni niz se može proslijediti kao:

void f ( int a [ ][4][3][2] );

4.5.Instrukcije za rad s nizovima i stringovima

Familija mikroprocesora 80x86 posjeduje više instrukcija dizajniranih za rad s nizovima. Ove instrukcije se zovu string instrukcije. One koriste indeksne registre (ESI i EDI) za obavljanje operacije i onda automatski inkrementiraju ili dekrementiraju jedan ili oba indeksna registra. Indikator pravca - direction flag (DF) u registru FLAGS određuje da li će se indeksni registri inkrementirati ili dekrementirati. Postoje dvije instrukcije koje modifikuju indikator pravca:

CLD postavlja indikator pravca na nulu. U ovom stanju, indeksni registri se inkrementiraju.

STD postavlja indikator pravca na jedan. U ovom stanju, indeksni registri se dekrementiraju.

Vrlo česta greška u 80x86 programiranju je zaboraviti eksplicitno postaviti indikator pravca u korektno stanje. To često vodi do koda koji radi većinu vremena (kada je indikator pravca u željenom stanju) ali ne radi u svim situacijama.

4.5.1.Čitanje i pisanje memorije

Najprostije string instrukcije čitaju ili pišu memoriju ili oboje. One mogu čitati ili pisati bajt, riječ ili duplu riječ u trenutku. Sl. 40 pokazuje ove instrukcije s kratkim opisom u pseudokodu šta one rade.

LODSB AL = [DS:ESI] STOSB [ES:EDI] = ALESI = ESI ± 1 EDI = EDI ± 1

LODSW AX = [DS:ESI] STOSW [ES:EDI] = AXESI = ESI ± 2 EDI = EDI ± 2

LODSD EAX = [DS:ESI] STOSD [ES:EDI] = EAXESI = ESI ± 4 EDI = EDI ± 4

Sl. 40: String instrukcije za čitanje i pisanje

Ima više stvari koje ovdje treba spomenuti. Prvo, ESI se koristi za čitanje a EDI za pisanje. To je lako zapamtiti ako se zna da SI je skraćenica od Source Index a DI od Destination Index. Dalje, registar koji čuva podatke je fiksni ( AL, AX ili EAX). Konačno, instrukcije za upis koriste ES za određivanje segmenta u koji se piše, a ne DS. U zaštićenom režimu rada, ovo obično ne predstavlja problem, pošto postoji samo jedan segment podataka i ES treba biti automatski inicijaliziran da referencira na njega (kao i DS ). Ipak, kod programiranja u realnom režimu rada, veoma je važno da programer inicijalizira registar ES na korektnu vrijednost selektora

Na Sl. 41 vidi se primjer instrukcija koje kopiraju jedan niz u drugi

61

Page 64: Sva predavanja na etfu iz predmeta kojeg drzi ribic

1 segment .data2 array1 dd 1,2,3,4,5,6,7,8,9,103

4 segment .bss5 array2 resd 106

7 segment .text8 cld ; ne zaboraviti!9 mov esi, array110 mov edi, array211 mov ecx, 1012 lp:13 lodsd14 stosd15 loop lp

Sl. 41: Upotreba instrukcija za čitanje i pisanje

Kombinacija instrukcija LODSx i STOSx (kao u linijama 13 i 14 sa Sl. 41) je vrlo česta. Zapravo, prva kombinacija se može izvesti jednom MOVSx string instrukcijom. Sl. 42 opisuje operacije koje obavlja ta instrukcija. Linije 13 i 14 sa Sl. 41 se mogu zamijeniti jednom MOVSD instrukcijom s istim efektom. Jedina razlika je što se registar EAX uopšte ne bi koristio u petlji.

MOVSB byte [ES:EDI] = byte [DS:ESI]

ESI = ESI ± 1 EDI = EDI ± 1

MOVSW word [ES:EDI] = word [DS:ESI] ESI = ESI ± 2

EDI = EDI ± 2 MOVSD dword [ES:EDI] = dword [DS:ESI] ESI = ESI ± 4

EDI = EDI ± 4

Sl. 42: Instrukcije za pomijeranje memorije

4.5.2.REP instrukcijski prefiksFamilija 80x86 ima specijalni instrukcijski prefiks koji se zove REP i koji se može koristiti za string instrukcije. Instrukcijski prefiks nije instrukcija, on je specijalni bajt koji se postavlja ispred string instrukcije koji modifikuje njeno ponašanje. (Postoje i drugi prefiksi za promjenu podrazumijevanih segmenata pri memorijskom pristupu ili načina adresiranja.)

Prefiks REP kaže procesoru da ponavlja narednu string instrukciju navedeni broj puta. Registar ECX se koristi za brojanje iteracija (kao za LOOP instrukciju). Upotrebom REP prefiksa, petlja sa Sl. 41 (linije 12 do 15) se mogu zamijeniti jednom linijom:

rep movsd

Sl. 43 prikazuje drugi primjer koji puni nulama dio memorije 1 segment .bss2 array resd 103

4 segment .text5 cld ; Ne zaboravi!6 mov edi, array7 mov ecx, 108 xor eax, eax9 rep stosd

Sl. 43: Punjenje niza nulama

62

Page 65: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.5.3.String instrukcije za poređenje

Sl. 44 prikazuje još neke string instrukcije koje se mogu koristiti za poređenje memorijske lokacije s drugom memorijskom lokacijom ili registrom. One su upotrebljive za poređenje ili pretraživanje nizova. One postavljaju FLAGS registar kao što to radi CMP instrukcija. Instrukcije CMPSx porede odgovarajuće memorijske lokacije, a SCASx pretražuje memorijske lokacije za specifičnu vrijednost. CMPSB poredi byte [DS:ESI] i byte [ES:EDI]

ESI = ESI ± 1 EDI = EDI ± 1

CMPSW poredi word [DS:ESI] i word [ES:EDI] ESI = ESI ± 2 EDI = EDI ± 2

CMPSD poredi dword [DS:ESI] i dword [ES:EDI] ESI = ESI ± 4 EDI = EDI ± 4

SCASB poredi AL i [ES:EDI] EDI ± 1 SCASW poredi AX i [ES:EDI] EDI ± 2 SCASD poredi EAX i [ES:EDI] EDI ± 4

Sl. 44: Instrukcije poređenja stringova

Sl. 45 prikazuje dio koda koji traži broj 12 u nizu duplih riječi. Instrukcija SCASD u liniji 10 uvijek uvećava EDI za 4, čak i ako je tražena vrijednost nađena. Tako, ako se želi naći adrese gdje je pronađena vrijednost 12 u nizu, neophodno je oduzeti 4 od EDI (kao u liniji 16).

1segment .bss2array resd 1003

4segment .text5cld6 mov edi, array ; pointer na početak niza7 mov ecx, 100 ; broj elemenata8 mov eax, 12 ; broj koji se traži9lp:1 scasd11 je found12 loop lp13; dio koji se obavlja ako nije nadjen14 jmp onward15found:16 sub edi, 4 ; edi sada pokazuje na 12 u nizu17; dio koji se obavlja ako je nadjen18onward:

Sl. 45: Primjer pretraživanja

4.5.4.Instrukcijski prefiksi REPx

Ima više drugih instrukcijskih prefiksa poput REP koji se mogu koristiti sa string instrukcijama poređenja. Sl. 46 prikazuje dva nova prefiksa i opisuje njihov rad.

REPE, REPZ ponavlja instrukciju dok je ZF postavljen ili najviše ECX puta REPNE, REPNZ ponavlja instrukciju dok je ZF obrisan ili najviše ECX puta

Sl. 46: REPx instrukcijski prefiksi

63

Page 66: Sva predavanja na etfu iz predmeta kojeg drzi ribic

REPE i REPZ su samo sinonimi za isti prefiks (kao što su to REPNE i REPNZ). Ako se ponovljena instrukcija za poređenje zaustavi zbog rezultata poređenja, indeksni registar je i dalje uvećan, a ECX umanjen, međutim FLAGS registar čuva stanje koje je prekinulo ponavljanje. Tako, moguće je koristiti Z indikator da se odredi da li je ponavljanje stalo zbog poređenja ili je ECX postao nula.

Sl. 47 pokazuje primjer koji poredi da li su dva bloka memorije identični. Instrukcija JE na liniji 7 provjerava rezultat prethodne instrukcije. Ako je ponovljeno poređenje stalo zato što su nađena dva nejednaka bajta, ZF će i dalje biti jednak nuli, i skok se neće desiti. Međutim, ako se poređenje zaustavilo zato što je ECX postao nula, ZF će postati 1, i skače se na labelu jednakosti.

1 segment .text2 cld3 mov esi, block1 ; adresa prvog bloka4 mov edi, block2 ; adresa drugog bloka5 mov ecx, size ; velicina blokova u bajtovima6 repe cmpsb ; ponavljaj dok je ZF flag jednak 17 je equal ; ako je ZF=1, blokovi su jednaki8 ; dio koji se izvodi ako blokovi nisu jednaki9 jmp onward10equal:11 ;dio koji se izvodi ako su blokovi jednaki12onward:

Sl. 47: Poređenje memorijskih blokova

64

Page 67: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.6.Pokretni zarez

4.6.1.Numerički koprocesor

Najraniji Intelovi procesori nisu imali hardversku podršku za operacije u pokretnom zarezu. To ne znači da nisu mogli obavljati te operacije. To samo znači da su morali obavljati ove operacije preko procedura koje obavljaju cjelobrojne operacije. Za ove rane sisteme Intel je proizveo poseban čip koji se zvao matematički koprocesor. Matematički koprocesor ima mašinske instrukcije koje obavljaju mnogo operacija u pokretnom zarezu daleko brže nego upotrebom softverskih procedura. (na ranim procesorima barem 10 puta brže!). Koprocesor za 8086/8088 se zvao 8087. Za 80286, postojao je 80287 a za 80386, koprocesor 80387. Procesor 80486DX uključio je matematički koprocesor direktno u 80486. Nakon Pentiuma, sve generacije 80x86 imaju ugrađen matematički koprocesor, ali se on i dalje programira kao posebna jedinica. Čak i raniji sistemi bez koprocesora mogu instalirati softver koji oponaša matematički koprocesor. Ovi oponašajući paketi se automatski aktiviraju kada program izvrši koprocesorsku instrukciju i izvrši softversku proceduru koja proizvodi isti rezultata kakav bi koprocesor imao (naravno, mnogo sporije). Numerički koprocesor ima osam registara za rad u pokretnom zarezu. Svaki registar ima 80 bitova podataka Brojevi u pokretnom zarezu se interno čuvaju u tim registrima u formatu od 80 bita, proširene preciznosti. Ovi registri se zovu ST0, ST1, ST2, . . . ST7. Registri za rad u pokretnom zarezu se koriste na drugi način nego cjelobrojni registri u glavnom procesoru. Registri za rad u pokretnom zarezu su organizovani kao stek. Poznato je da je stek Last-In First-Out (LIFO) lista. ST0 uvijek pokazuje na vrijednost na vrhu steka. Postojeći brojevi se guraju na steku da se napravi mjesto za novi broj..

Postoji i statusni registar u numeričkom koprocesoru. On ima više indikatora. Samo 4 indikatora, koja se koriste za poređenje, će ovdje biti opisana: C0, C1, C2 i C3.

Da se lako razlikuju normalne instrukcije procesora od koprocesorskih, svi mnemonici koprocesora počinju sa F.

4.6.2.Učitavanje i smještanje

Ima više instrukcija koje učitavaju podatke na vrh koprocesorskog steka: FLD source Učitava broj u pokretnom zarezu iz memorije na vrh koprocesorskog steka. Izvor može

biti broj u jednostrukoj, dvostrukoj ili proširenoj preciznosti ili koprocesorski registar.FILD source Čita cijeli broj iz memorije, konvertuje ga u pokretni zarez i smješta rezultat na vrh

koprocesorskog steka. Izvor može biti 16bitna, 32 bitna ili 64 bitna riječFLD1 Smješta konstantu 1 na vrh koprocesorskog stekaFLDZ Smješta konstantu 0 na vrh koprocesorskog steka

Postoje također instrukcije koje smještaju podatke sa koprocesorskog steka u memoriju. Neke od ovih instrukcija također uklanjaju broj sa koprocesorskog steka kako ih i smještaju.

FST dest Smješta vrh FP steka (ST0) u memoriju. Odredište može biti broj u jednostrukoj, dvostrukoj preciznosti ili koprocesorski registar.

FSTP dest Smješta vrh steka (ST0) u memoriju kao FST; ali nakon što je broj smješten, njegova vrijednost se skida sa steka. Odredište može biti broj u jednostrukoj, dvostrukoj ili proširenoj preciznosti ili koprocesorski registar

FIST dest Smješta vrijednost na vrhu koprocesorskog steka konvertovanu u cijeli broj u memoriju. Odredište može biti riječ ili dupla riječ. Sam koprocesorski stek se ne mijenja. Kako se broj u pokretnom zarezu prevodi u cijeli broj, zavisi od nekih bita u koprocesorskoj kontrolnoj riječi. Podrazumijevana situacija je da se broj zaokružuje na najbližu cijeli broj prilikom konverzije. No ovo se može promijeniti instrukcijama FSTCW (Store Control Word) i FLDCW (Load Control Word) .

FISTP dest Isto kao FIST sem dvije stvari. Vrh koprocesorskog steka se uklanja i odredište može biti

65

Page 68: Sva predavanja na etfu iz predmeta kojeg drzi ribic

četverostruka (64 bitna) riječ. Postoje još dvije instrukcije koje mogu pomjeriti ili ukloniti podatke na samom koprocesorskom steku.

FXCH STn Razmjenjuje vrijednosti ST0 i STn na koprocesorskom steku(n je broj registra između 1 i 7).FFREE STn Oslobađa registar na koprocesorskom steku, čineći ga praznim ili nekorištenim.

4.6.3.Osnovne aritmetičke operacije

Svaka operacija sabiranja računa sumu registra ST0 i drugog operanda. Rezultat se uvijek smješta u koprocesorski registar. FADD src ST0 += src . Izvor može biti bilo koji koprocesorski registar ili broj jednostruke ili

dvostruke preciznosti u memoriji.FADD dest, ST0 dest+= ST0. Odredište može biti bilo koji koprocesorski registar.FADDP dest FADDP dest, ST0

dest+= ST0 i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FIADD src ST0 += (float) src . Sabira cijeli broj sa ST0. Izvor mora biti riječ ili dupla riječ u memoriji.

Sl. 48 prikazuje kratki dio koda koji sabira elemente niza duplih riječi. 1 segment .bss2 array resq SIZE3 sum resq 14

5 segment .text6 mov ecx, SIZE7 mov esi, array8 fldz ; ST0 = 09lp:10 fadd qword [esi] ; ST0 += *(esi)11 add esi, 8 ; naredni broj12 loop lp13 fstp qword sum ; smjesti rezultat u sum

Sl. 48: Primjer sumiranja niza

Na linijama 10 i 13, mora se specificirati veličina memorijskog operanda. Inače asembler neće znati da li je memorijski operand bio float (dword) ili double (qword).

Postoji duplo više operacija oduzimanja nego sabiranja, jer je kod oduzimanja redoslijed bitan (tj. a + b = b + a, ali a - b ≠ b - a!). Za svaku instrukciju postoji alternativna koja oduzima u obrnutom redoslijedu. Sve instrukcije za oduzimanje u obrnutom redoslijedu se završavaju sa R ili RP.

FSUB src ST0 -= src . Izvor može biti bilo koji koprocesorski registar ili broj jednostruke ili dvostruke preciznosti u memoriji.

66

Page 69: Sva predavanja na etfu iz predmeta kojeg drzi ribic

FSUBR src ST0 = src-ST0. Izvor može biti bilo koji koprocesorski registar ili broj jednostruke ili dvostruke preciznosti u memoriji.

FSUB dest, ST0 dest-= ST0. Odredište može biti bilo koji koprocesorski registar.FSUBR dest, ST0 dest= ST0 - dest . Odredište može biti bilo koji koprocesorski registar.FSUBP dest FSUBP dest, ST0

dest-= ST0 i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FSUBRP dest FSUBRP dest, ST0

dest= ST0 - dest i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FISUB src ST0 -= (float) src . Oduzima cijeli broj od ST0. Izvor mora biti riječ ili dupla riječ u memoriji.

FISUBR src ST0 = (float) src-ST0. Oduzima ST0 od cijelog broja. Izvor mora biti riječ ili dupla riječ u memoriji.

Instrukcije množenja su potpuno analogne instrukcijama sabiranja, jer su obije operacije komutativne. FMUL src ST0 *= src . Izvor može biti bilo koji koprocesorski registar ili broj jednostruke

ili dvostruke preciznosti u memoriji.FMUL dest, ST0 dest*= ST0. Odredište može biti bilo koji koprocesorski registar.FMULP dest FMULP dest, ST0

dest*= ST0 i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FIMUL src ST0 *= (float) src . Množi cijeli broj sa ST0. Izvor mora biti riječ ili dupla riječ u memoriji.

67

Page 70: Sva predavanja na etfu iz predmeta kojeg drzi ribic

Ne iznenađuje da su instrukcije dijeljenja analogne instrukcijama oduzimanja. Dijeljenje s nulom rezultuje beskonačnim rezultatom.

FDIV src ST0 /= src . Izvor može biti bilo koji koprocesorski registar ili broj jednostruke ili dvostruke preciznosti u memoriji.

FDIVR src ST0 = src/ ST0. Izvor može biti bilo koji koprocesorski registar ili broj jednostruke ili dvostruke preciznosti u memoriji.

FDIV dest, ST0 dest/= ST0. Odredište može biti bilo koji koprocesorski registar.FDIVR dest, ST0 dest= ST0 / dest . Odredište može biti bilo koji koprocesorski registar.FDIVP dest FDIVP dest, STO

dest/= ST0 i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FDIVRP dest FDIVRP dest, STO

dest= ST0 / dest i pokupi broj s koprocesorskog steka. Odredište može biti bilo koji koprocesorski registar.

FIDIV src ST0 /= (float) src . Dijeli ST0 s cijelim brojem. Izvor mora biti riječ ili dupla riječ u memoriji.

FIDIVR src ST0 = (float) src/ ST0. Dijeli cijeli broj s ST0. Izvor mora biti riječ ili dupla riječ u memoriji.

FCOM src Poredi ST0 i src . Izvor može biti koprocesorski registar ili float ili double u memoriji.

FCOMP src Poredi ST0 i src i pokupi broj s koprocesorskog steka. Izvor može biti koprocesorski registar ili float ili double u memoriji.

FCOMPP Poredi ST0 i ST1, i pokupi broj s koprocesorskog steka dva puta.FICOM src Poredi ST0 i (float) src . Izvor može biti cjelobrojna riječ ili dupla riječ u

memoriji. FICOMP src Poredi ST0 i (float)src i pokupi broj s koprocesorskog steka. Izvor može biti

cjelobrojna riječ ili dupla riječ u memoriji. FTST poredi ST0 i 0.

Ove instrukcije mijenjaju bitove C0, C1, C2 i C3 koprocesorskog statusnog registra. Nažalost, nije moguće da CPU direktno pristupa ovim bitovima. Instrukcije uslovnih skokova koriste FLAGS registar, a ne koprocesorski status registar. Ipak relativno je jednostavno prebaciti bite statusne riječi u odgovarajuće bite FLAGS registra koristeći neke nove instrukcije:

FSTSW dest Smješta koprocesorsku statusnu riječ u bilo riječ u memoriji ili AX registar. SAHF Smješta AH registar u FLAGS registar.LAHF Čita AH registar sa bitima FLAGS registra.

Sl. 49 prikazuje kratki odsječak koda. Linije 5 i 6 prenose C0, C1, C2 i C3 bite koprocesorske statusne riječi u FLAGS registar. Biti se prenose tako da su analogne rezultatu poređenja dva nepredznačena broja. To je razlog zašto linija 7 koristi instrukcije JNA.

; Dio koda koji pokazuje upotrebu FICOMP FLD NUM1FLD NUM2FICOMPFSTSW AXSAHFJNA NUM1ISGREATER

Sl. 49 Upotreba FPU testiranja sa FICOMP

Pentium Pro (i kasniji procesori) podržavaju dvije nove instrukcije poređenja koje direktno mijenjaju procesorski FLAGS registar.

FCOMI src Poredi ST0 i src . Izvor mora biti koprocesorski registar. FCOMIP src Poredi ST0 i src , src i pokupi broj s koprocesorskog steka. Izvor mora biti koprocesorski

registar.

68

Page 71: Sva predavanja na etfu iz predmeta kojeg drzi ribic

4.6.4.Razne instrukcije

Ova sekcija pokriva druge, razne instrukcije koje omogućava koprocesor. FCHS ST0 = - ST0 Mijenja predznak ST0FABS ST0 = |ST0| Uzima apsolutnu vrijednost ST0

FSQRT Kvadratni korijen ST0FSIN Sinus ST0FCOS Kosinus ST0FPTAN Tangens ST0FPATAN Arkus tangens ST0FYL2X Logaritam od ST0, po bazi 2FSCALE ST0 = ST0 × 2

ST1 množi ST0 sa stepenom broja 2 brzo. ST1 se ne uklanja s

koprocesorskog steka..

4.7.Strukture

Strukture se koriste u C-u da grupišu vezane podatke u složenu varijablu. Ova tehnika ima više prednosti.

1. Objašnjava program, pokazujući da su podaci definisani u strukturi blisko vezani.

2. Pojednostavljuje prosljeđivanje podataka funkcijama. Umjesto prosljeđivanja više varijabli odvojeno, one se mogu proslijediti kao jedna jedinica.

3. Povećava lokalnost koda.

Sa asemblerske tačke gledišta, struktura se može smatrati kao niz s elementima varijabilne veličine. Elementi klasičnih nizova su uvijek iste veličine i tipa. Ovo svojstvo omogućava račun adrese elementa znajući njegovu početnu adresu, veličinu elementa i željeni indeks elementa. Elementi strukture ne moraju biti iste veličine (i obično nisu). Zbog ovoga, svaki element strukture mora biti eksplicitno specificiran koristeći oznaku (ili ime) umjesto numeričkog indeksa. U asembleru se elementu strukture može pristupiti na sličan način kao elementu niza. Da se pristupi elementu, treba znati startnu adresu strukture i relativnu poziciju tog elementa od početka strukture. Međutim, za razliku od niza, gdje se ova pozicija mora računati indeksom elementa, adresa elementa strukture se računa uz pomoć pozicije koju generiše kompajler.

Na primjer, pogledati sljedeću strukturu:

struct S {short int x ; /* 2-bajta cijeli broj */int y ; /* 4-bajta cijeli broj */double z ; /* 8-bajta realni */};

Sl. 50 pokazuje kako bi varijabla S mogla izgledati u računarskoj memoriji. Adresa Varijabla0 v.x2 v.y6 v.z14 Kraj

Sl. 50 Raspored strukture

ANSI C standard definiše da su elementi strukture u memoriji poredane u istom redoslijedu kako su definisani u definiciji struct. On također definiše da je prvi element na samom početku strukture (tj. pozicija

69

Page 72: Sva predavanja na etfu iz predmeta kojeg drzi ribic

0). Standard definiše i koristan makro u zaglavlju stddef.h pod imenom offsetof(). Ovaj makro računa i vraća poziciju bilo kojeg elementa strukture. Makro zahtijeva dva elementa, prvi je ime tipa strukture, drugi je ime elementa čija se pozicija traži. Tako, rezultat offsetof(S, y) bi bio 2 sa Sl. 50.

Ako bi se koristio makro offsetof za traženje pozicije polja y korištenjem gcc kompajlera, primjetiće se da je on vratio vrijednost koju vraća 4, a ne 2! Zašto? Zato što gcc (i mnogi drugi kompajleri) poravnavaju vrijednost na duplu riječ. U 32-bitnom zaštićenom režimu, CPU brže čita tridesetdvobitne podatke u memoriji ako podaci počinju na granici duple riječi. Sl. 51 prikazuje kako struktura S zaista izgleda kada se koristi gcc.

. Adresa Varijabla0 v.x2 Neiskorišteno4 v.y8 v.z16 Kraj

Sl. 51 Raspored strukture sa poravnavanjem

Kompajler ubacuje dva nekorištena bajta u strukturu da poravna y i z na granicu duple riječi. To pokazuje zašto je razumna ideja koristiti offsetof za račun pozicija, umjesto da ih programer sam računa kada se koriste strukture definisane u C-u.

Osmobitni podaci se tipično ne moraju poravnavati na adrese koje odgovaraju duploj riječi, jer za njihovo čitanje nema razlike u brzini pristupa ako su adrese poravnate ili ne. Šesnaestbitni podaci se poravnavaju na granice šesnaestbitne riječi.

Naravnom ako se struktura koristi samo u asembleru, programer može sam odrediti pozicije. Ipak, ako se povezuju C i asemblerski jezik, veoma je važno da se asemblerski kod i C kod slažu po pitanju pozicija elemenata. Dodatna komplikacija može biti da različiti C kompajleri mogu dati različite pozicije podataka. Ipak ANSI C standard ne definiše kako to treba biti urađeno, i stoga različiti kompajleri to rade na različit način.

70