Az egész szám egy absztrakció, amely egész számokat reprezentál, például 4, -2, 0 vagy 64532.
Ahhoz, hogy egy egész számot bájtok sorozataként ábrázoljunk, a bináris jelölést használjuk. Ebben a jelölésben a sorozat minden bitje a kettő egy-egy hatványát reprezentálja, és az érték növekszik, ahogy a bit indexe jobbról balra növekszik.
Ha a szám csak nemnegatív lehet, előjel nélküli számnak nevezzük.
Az előjel nélküli számokat közvetlenül a sorozatukban beállított összes bithez tartozó kettőhatványok összegeként ábrázoljuk.
A regiszterben ábrázolható nemnegatív egész számok tartománya 0-tól (egyetlen bit sincs beállítva) 2⁶⁴ - 1-ig (mind a 64 bit be van állítva) terjed.
Egy előjel nélküli szám nagyobb méretre szélesítése úgy történik, hogy az összes felső bitet 0-val töltjük fel, így egyetlen új bit sem járul hozzá az értékhez.
Ezt nulla-kiterjesztésnek nevezzük.
A movzx utasítás (z a nullát jelöli) nulla-kiterjeszti a 8 bites vagy 16 bites forrásoperandust egy nagyobb céloperandusra.
A 32 bites forrásoperandust egy egyszerű mov mindig nulla-kiterjeszti a céloperandus mind a 64 bitjére.
Ha egy egész szám pozitív vagy negatív értéket vehet fel, előjeles számnak nevezzük.
A negatív számok ábrázolására az x86-64 a kettes komplemens reprezentációt használja.
A kettes komplemensben az előjeles számokat szintén a beállított bitekhez tartozó kettőhatványok összegeként ábrázoljuk. Ha azonban a legfelső bit be van állítva, akkor azt a többihez képest nem hozzáadjuk, hanem kivonjuk.
Mivel ez a bit nagyobb értéknek felel meg, mint az összes többi összege, a gyakorlatban ez azt jelenti, hogy az ilyen bitet beállított szám mindig negatív. Ezt a különleges bitet előjelbitnek nevezzük.
Egy előjeles szám nagyobb méretre szélesítése azt jelenti, hogy minden új felső bitet az előjelbit másolatával töltünk fel, így az érték megmarad. Ezt előjel-kiterjesztésnek nevezzük.
A movsx utasítás (s az előjelet jelöli) előjel-kiterjeszti a 8 bites vagy 16 bites forrásoperandust egy nagyobb céloperandusra.
A movsx movsxd nevű változata ugyanezt teszi 32 bites forrásoperandusról 64 bites céloperandusra.
A neg utasítással megváltoztathatod egy szám előjelét.
Assemblyben nincs mód arra, hogy megmondjuk, egy bájtsorozat előjeles vagy előjel nélküli számot reprezentál-e. A programozó felelőssége, hogy jelentést adjon ezeknek a bájtoknak.
A megjegyzések használata nagy segítség lehet ebben a feladatban.
Egy korábbi fogalomban említettük, hogy egy állandó szám, például 4 vagy -15, forrásoperandusként használható sok utasításnál.
Ezeket a számokat azonnali értékeknek nevezzük.
Az azonnali értéket nem regiszterben vagy memóriában tároljuk: magában az utasításban van kódolva. A legtöbb utasításban a számára fenntartott hely csak 32 bit széles, függetlenül attól, mekkora a céloperandus.
Amikor a céloperandus 64 bit széles, ezt a 32 bitet előjel-kiterjesztjük, hogy kitöltse. Az operandus felső fele teljesen megtelik az azonnali érték legfelső bitjének másolataival, így csak egy 32 bites előjeles egész szám tartományába eső szám írható le:
add rax, -1 ; the immediate is sign-extended, so all 64 bits of rax are affected
add rax, 2147483647 ; the largest immediate an instruction like this accepts
Az ezen a tartományon kívül eső szám nem használható azonnali értékként.
Kivétel ez alól a mov, amely teljes 64 bites azonnali értéket is fogadhat, ha a céloperandus regiszter.
Ha 64 bites azonnali értékre van szükség, először a mov utasítással töltsd be egy regiszterbe, majd használd azt a regisztert:
mov rax, 3435973837 ; this works, mov can take a 64-bit immediate
mov rdx, 18446744073709551615 ; the largest immediate mov accepts
sub rdx, rax
Vedd figyelembe, hogy egy negatív azonnali érték és az ugyanolyan bitreprezentációjú előjel nélküli szám egyenértékű, és pontosan ugyanarra az értékre fordul:
mov rax, -1 ; rax = 18446744073709551615
mov rax, 18446744073709551615 ; rax = -1
Két szám összeadása az add utasítással számítható ki.
Van egy inc egyoperandusú utasítás is, amely 1-et ad hozzá az operandusában lévő értékhez:
inc rax ; rax = rax + 1
Két egész szám összeadása ugyanúgy működik előjel nélküli és előjeles számok esetén is.
Két egész szám kivonása a sub utasítással történik.
Van egy dec egyoperandusú utasítás is, amely 1-et von ki az operandusában lévő értékből:
dec rax ; rax = rax - 1
Két egész szám kivonása szintén ugyanúgy működik előjel nélküli és előjeles számok esetén is.
Két különböző utasítás létezik két szám szorzására x86-64-en.
Főszabályként az előjel nélküli szorzás a mul, míg az előjeles szorzás az imul utasítást használja.
A mul utasítás a következő egyoperandusú formát veszi fel, ahol src a forrásoperandus:
mul src
Az imul utasítás egyoperandusú, kétoperandusú vagy háromoperandusú formát vehet fel:
imul src
imul dest, src
imul dest, src1, src2
Egyoperandusú formában a szorzás végrehajtásához két regisztert használunk implicit módon: rax és rdx.
Ha a szorzás két 64 bites számot érint, az eredmény alsó 64 bitje rax-ben, a felső 64 bitje pedig rdx-ben lesz.
Ezt általában rdx:rax-nek nevezik, jelezve, hogy mindkét regiszter együttműködve használatos:
mul rcx ; rax = lower 64 bits of rax * rcx
; rdx = upper 64 bits of rax * rcx
Ugyanez történik más operandusméreteknél is.
Így például ha két 32 bites számot szorzunk, eax-et és edx-et használjuk.
Kivétel a két bájt közötti szorzás.
Ebben az esetben dl:al helyett ax-et használunk.
Az ax alsó része (al) kapja a szorzat alsó 8 bitjét, a felső része (ah) pedig a felső 8 bitet.
A szorzásban implicit módon használt regiszterek, például rax és rdx, mindig felülíródnak.
Ha ezekre a regiszterekre később szükség lesz, az értékeiket a művelet előtt mentsd el.
Az imul kétoperandusú formájának van egy explicit céloperandusa, és a szokásos szintaxist követi.
A rdx nem használatos.
Ehelyett az eredményt levágjuk, hogy elférjen a céloperandusban.
imul r8, r9 ; r8 = lower 64 bits of r8 * r9
Az imul háromoperandusú formájának két forrásoperandusa van, amelyek közül a második mindig egy azonnali érték (állandó szám).
Mindkét forrásoperandust megszorozzuk, az eredményt levágjuk, és a céloperandusba helyezzük:
imul r8, r9, 100 ; r8 = lower 64 bits of r9 * 100
Vedd figyelembe, hogy a céloperandus nem vesz részt a szorzásban. Csupán fogadja az eredményt.
A kétoperandusú és a háromoperandusú szorzás is levágja az eredményt, hogy elférjen a céloperandus méretében.
Az egyoperandusú szorzás megőrzi a teljes tartományt, de az eredmény általában két regiszterre oszlik, rdx-re és rax-re.
Ezért néha hasznos az operandusokat a szorzás előtt kiszélesíteni, hogy a teljes szorzat elférjen egyetlen regiszterben. Az előjel nélküli operandus nulla-kiterjesztést kap, az előjeles pedig előjel-kiterjesztést:
movzx eax, di ; di and si hold unsigned 16-bit numbers
movzx ecx, si
mul ecx ; the 32-bit product fits in eax, and edx is cleared
Ahogy a szorzásnál, úgy az osztásnál is két utasítás létezik két szám közötti osztás elvégzésére.
Az előjel nélküli osztás a div, míg az előjeles osztás az idiv utasítást használja.
Mindkét utasítás csak egy operandussal működik:
div src
idiv src
A 16 bites, 32 bites és 64 bites osztás osztalékként dx:ax, edx:eax, illetve rdx:rax értéket használ.
Ezekben az esetekben mindkét regiszter együttműködve hoz létre egy 2N bites értéket, ahol N a művelet mérete (16 bit, 32 bit vagy 64 bit).
Ezt az értéket elosztjuk a forrásoperandussal.
A hányados az ax, eax vagy rax regiszterbe kerül, a maradék pedig a dx, edx vagy rdx regiszterbe, a művelet méretének megfelelően.
A bájtok közötti osztás különleges: dl:al helyett ax-et használunk.
Az ax alsó 8 bitje (al) kapja a művelet hányadosát, a felső 8 bitje (ah) pedig a maradékot.
Vedd figyelembe, hogy az osztalék minden bitjét megfelelően be kell állítani az osztás előtt.
Az rdx-ben (vagy 8 bites osztásnál ah-ban) beállított bármely bit hozzájárul az osztandó értékhez.
Előjel nélküli osztásnál, amikor az osztandó érték elfér az alsó felében, a felső felét törölni kell.
Bármely utasítás megteszi, amely törli ezeket a biteket.
Például a mov edx, 0 törli a felső biteket 32 bites osztásnál.
Előjeles osztásnál ehelyett előjel-kiterjesztést kell alkalmazni.
Vannak utasítások, amelyek automatizálják ezt a folyamatot: cbw, cwd, cdq és cqo.
Az első beállítja az ah bitjeit az al előjele szerint.
A többiek előjel-kiterjesztést végeznek ax-ből dx-be, eax-ből edx-be, illetve rax-ből rdx-be.
Az osztásban implicit módon használt regiszterek, például rax és rdx, mindig felülíródnak.
Ha ezekre a regiszterekre később szükség lesz, az értékeiket az osztás előtt mentsd el.
Egy helyi üzlet nagyobb raktárba költözteti a készletét. Téged bíztak meg azzal, hogy csomagolj be és szállíts el mindent.
Négy részfeladatod van, mindegyik a szállítás megszervezéséhez kapcsolódik.
Ezek az ebben a fogalomban említett utasítások:
| Utasítás | Leírás |
|---|---|
| add a, b | a = a + b |
| inc a | a = a + 1 |
| sub a, b | a = a - b |
| dec a | a = a - 1 |
| imul a | rdx:rax = a * rax (előjeles) |
| imul a, b | a = a * b (előjeles, csonkolt) |
| imul a, b, c | a = b * c (előjeles, csonkolt) |
| mul a | rdx:rax = a * rax (előjel nélküli) |
| div a | rax = hányados, rdx = rdx:rax / a maradéka (előjel nélküli) |
| idiv a | rax = hányados, rdx = rdx:rax / a maradéka (előjeles) |
| movzx a, b | a = b, a plusz bitekhez 0-t adva |
| movsx a, b | a = b, a plusz bitekhez 1-et adva, ha b < 0, egyébként 0-t |
Ne feledd, hogy ugyanahhoz a regiszterhez különböző méretekkel is hozzáférhetsz, ha megváltoztatod az operandus nevét.
Például: rax (64 bites), eax (32 bites), ax (16 bites), al (8 bites).
A teljes táblázathoz lapozd fel az előző fogalmat.
A termékeket dobozokba csomagolják, amelyeket a súlyukkal kell felcímkézni. Nincs a közelben mérleg, de szerencsére tudod, hogy egy-egy termék átlagosan mennyit nyom.
A könnyebb rendszerezés érdekében egy dobozban csak két különböző termék darabjai lehetnek.
Definiálj egy get_box_weight függvényt, amely visszaadja egy doboz összsúlyát g-ban.
A függvény a következő paramétereket veszi át, ebben a sorrendben:
g-bang-banVedd figyelembe, hogy egy üres doboz súlya 500 g.
A WEIGHT_OF_EMPTY_BOX konstans a megoldásfájl tetején van definiálva.
Példa:
get_box_weight(30, 40, 50, 20);
// => 2700
Minden argumentum 16 bites nemnegatív egész szám, a visszatérési érték pedig egy 32 bites nemnegatív egész szám.
A dobozokat egymásra rakják, és teherautóval szállítják az új raktárba. A teherautóban viszont véges a függőleges hely.
Definiálj egy max_number_of_boxes függvényt, amely visszaadja, hogy egy adott magasságú dobozból hány fér el egymásra rakva a teherautóban.
A függvény paraméterként a doboz magasságát kapja meg cm-ben.
Vedd figyelembe, hogy a teherautó belső magassága 300 cm.
A TRUCK_HEIGHT konstans a megoldásfájl tetején van definiálva.
Példa:
max_number_of_boxes(30);
// => 10
Az argumentum és a visszatérési érték is 8 bites nemnegatív egész szám.
A doboz magassága mindig legalább 2, így az eredmény elfér 8 biten.
Az új raktárban van egy ellenőrzőlista, amelyen termékenként fel van tüntetve, hány darab vár még a számbavételre. Minden oda beszállított új doboz esetében ki kell számolnod a dobozban lévő minden termék új értékét az ellenőrzőlistán.
Definiálj egy items_to_be_moved függvényt, amely visszaadja, hogy egy adott termékből hány darabot kell még az új raktárba szállítani.
A függvény a következő paramétereket veszi át, ebben a sorrendben:
Példa:
items_to_be_moved(76532, 120);
// => 76412
Az argumentumok 32 bites nemnegatív egész számok. A visszatérési érték egy 32 bites egész szám. Ha a folyamat során hiba történik, előfordulhat, hogy az eredmény negatív szám.
A fizetésed attól függ, hány dobozt szállítottál el, és hány fuvarra volt szükség.
Minden dobozért 5 dollárt kapsz, és minden fuvarért 220 dollárt kapsz.
A PAY_PER_BOX és a PAY_PER_TRUCK_TRIP konstans a megoldásfájl tetején van definiálva.
Ne feledd, hogy a fizetés egy részét előre megkaphattad a kezdeti költségek fedezésére, és ezt az előre kifizetett összeget le kell vonni a végső fizetésből. Emellett néhány termékre nincs biztosítás, és a fizetésedet csökkenti az ilyen termékekből összetört vagy elveszett darabok értéke is. Ha nem figyelsz, akár még tartozhatsz is!
Ez azt jelenti, hogy a nettó összeg, amit kapnod kell, vagy amivel tartozol:
net = boxes * PAY_PER_BOX + trips * PAY_PER_TRUCK_TRIP - up_front - broken_items * item_value
Ezt a fizetést, vagy tartozást, egyenlően osztjátok el te és a felbérelt munkások között.
A megmaradó pénz, vagy tartozás, a tiéd.
Például ha a nettó összeg 100, amelyet 6 ember osztozik meg (te és 5 munkás), akkor 20-at kapsz (100/(5 + 1) = 16, plusz a megmaradó 4).
Definiálj egy calculate_payment függvényt, amely visszaadja, hogy a végén mennyit kell kapnod, vagy fizetned.
A függvény a következő paramétereket veszi át, ebben a sorrendben:
Példa:
calculate_payment(2000, 1000, 5, 21, 2, 1);
// => 2029
A visszatérési érték egy 64 bites egész szám.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) x86-64 Assembly nyelvet 22 fogalom130 feladat segítségével, valódi emberi mentorálással, mindez ingyen.