Assembly
Az assembly nyelv olyan programozási nyelv, amellyel közvetlenül meg lehet mondani a számítógépnek, hogy mit csináljon. Az assembly nyelv majdnem pontosan olyan, mint a számítógép által érthető gépi kód, azzal a különbséggel, hogy számok helyett szavakat használ. A számítógép nem igazán képes közvetlenül megérteni egy assembly programot. A programot azonban könnyen gépi kóddá tudja alakítani azáltal, hogy a program szavait az általuk jelölt számokkal helyettesíti. Az ezt végző programot asszemblernek nevezik.
Az assembly nyelven írt programok általában utasításokból állnak, amelyek olyan apró feladatok, amelyeket a számítógép a program futtatása közben végrehajt. Azért hívják őket utasításoknak, mert a programozó ezekkel utasítja a számítógépet, hogy mit tegyen. A számítógép azon része, amely az utasításokat követi, a processzor.
A számítógép assembly nyelve alacsony szintű nyelv, ami azt jelenti, hogy csak olyan egyszerű feladatok elvégzésére használható, amelyeket a számítógép közvetlenül megért. Az összetettebb feladatok elvégzéséhez meg kell mondani a számítógépnek az egyes egyszerű feladatokat, amelyek az összetett feladat részét képezik. Egy számítógép például nem érti, hogyan kell kinyomtatni egy mondatot a képernyőjére. Ehelyett egy assembly nyelven írt programnak kell megmondania, hogyan kell elvégezni a mondat kinyomtatásához szükséges összes apró lépést.
Egy ilyen assembly program sok-sok utasításból állna, amelyek együttesen olyasmit tesznek, ami egy ember számára nagyon egyszerűnek és alapvetőnek tűnik. Ez megnehezíti az emberek számára egy assembly program olvasását. Ezzel szemben egy magas szintű programozási nyelvben egyetlen utasítás, például a PRINT "Helló, világ!" lehet, amely utasítja a számítógépet, hogy végezze el az összes apró feladatot helyettünk.
Az assembly nyelv fejlődése
Amikor a számítástechnikusok először építettek programozható gépeket, azokat közvetlenül gépi kóddal programozták, ami egy számsorozat, amely utasította a számítógépet, hogy mit tegyen. A gépi nyelv megírása nagyon nehéz volt, és sokáig tartott, ezért végül elkészült az assembly nyelv. Az összeszerelési nyelv könnyebben olvasható az ember számára, és gyorsabban írható, de még mindig sokkal nehezebben használható, mint egy magas szintű programozási nyelv, amely az emberi nyelvet próbálja utánozni.
Programozás gépi kódban
A gépi kódban való programozáshoz a programozónak tudnia kell, hogy az egyes utasítások hogyan néznek ki binárisan (vagy hexadecimálisan). Bár a számítógép számára könnyű gyorsan rájönni, hogy mit jelent a gépi kód, a programozó számára ez nehéz. Minden utasításnak több formája is lehet, amelyek az emberek számára csak számok halmazának tűnnek. Bármilyen hibát követ el valaki a gépi kód írása közben, csak akkor veszi észre, amikor a számítógép rosszul csinálja a dolgot. A hiba kitalálása azért nehéz, mert a legtöbb ember nem tudja ránézésre megmondani, hogy mit jelent a gépi kód. Egy példa arra, hogyan néz ki a gépi kód:
05 2A 00
Ez a hexadecimális gépi kód azt mondja egy x86-os számítógép processzorának, hogy adjon hozzá 42-t az akkumulátorhoz. Egy ember számára nagyon nehéz elolvasni és megérteni, még akkor is, ha az illető ismeri a gépi kódot.
Assembly nyelv használata helyett
Az assembly nyelven minden utasítás egy rövid szóval, úgynevezett mnemonikával írható le, amelyet más dolgok, például számok vagy más rövid szavak követnek. A mnemonikát azért használják, hogy a programozónak ne kelljen megjegyeznie a gépi kódban szereplő pontos számokat, amelyek ahhoz szükségesek, hogy a számítógépnek megmondja, hogy csináljon valamit. Az assembly nyelven használt mnemonikákra példa az add, amely adatokat ad hozzá, és a mov, amely adatokat mozgat egyik helyről a másikra. Mivel a "mnemonikus" szó nem gyakori, helyette néha az utasítás típusa vagy csak utasítás kifejezés használatos, gyakran helytelenül. Az első szó utáni szavak és számok további információt adnak arról, hogy mit kell tenni. Például az összeadást követő dolgok azt jelenthetik, hogy milyen két dolgot kell összeadni, a mozgatást követő dolgok pedig azt, hogy mit kell mozgatni és hová kell tenni.
Például az előző szakaszban szereplő gépi kód (05 2A 00) assemblyben a következőképpen írható le:
Az assembly nyelv azt is lehetővé teszi a programozók számára, hogy a program által használt tényleges adatokat egyszerűbb módon írják meg. A legtöbb assembly nyelv támogatja a számok és a szöveg egyszerű elkészítését. A gépi kódban minden egyes különböző típusú számot, például pozitív, negatív vagy decimális, kézzel kellene binárissá alakítani, a szöveget pedig betűről betűre, számként kellene definiálni.
Az assembly nyelv a gépi kód úgynevezett absztrakcióját nyújtja. Az assembly használatakor a programozóknak nem kell ismerniük a számok jelentésének részleteit a számítógép számára, ezt az assembler számítja ki helyettük. Az összeszerelési nyelv valójában még mindig lehetővé teszi a programozó számára, hogy a processzor minden olyan funkcióját használja, amelyet a gépi kóddal is használhat. Ebben az értelemben az assembly nyelvnek van egy nagyon jó, ritka tulajdonsága: ugyanúgy képes kifejezni dolgokat, mint az, amit absztrahál (gépi kód), miközben sokkal könnyebben használható. Emiatt a gépi kódot szinte soha nem használják programozási nyelvként.
Szétszerelés és hibakeresés
Amikor a programok elkészülnek, már átalakították őket gépi kóddá, hogy a processzor ténylegesen le tudja futtatni őket. Néha azonban, ha a programban hiba (hiba) van, a programozók azt akarják, hogy a gépi kód egyes részei meg tudják mondani, hogy mit csinálnak. A disassemblerek olyan programok, amelyek segítenek a programozóknak ebben azáltal, hogy a program gépi kódját visszaalakítják assembly nyelvre, ami sokkal könnyebben érthető. A disassemblerek, amelyek a gépi kódot assembly nyelvvé alakítják, az assemblerek ellenkezőjét teszik, amelyek az assembly nyelvet gépi kóddá alakítják.
Számítógépes szervezés
Ahhoz, hogy megértsük, hogyan szerveződnek a számítógépek, hogyan működnek látszólag nagyon alacsony szinten, szükség van egy assembly nyelvű program működésének megértésére. A legegyszerűbb szinten a számítógépek három fő részből állnak:
- főmemória vagy RAM, amely adatokat és utasításokat tartalmaz,
- egy processzor, amely az utasítások végrehajtásával feldolgozza az adatokat, és
- bemenet és kimenet (néha rövidítve I/O), amelyek lehetővé teszik a számítógép számára, hogy kommunikáljon a külvilággal, és adatokat tároljon a főmemórián kívül, hogy később visszakapja az adatokat.
Fő memória
A legtöbb számítógépben a memória bájtokra van felosztva. Minden bájt 8 bitet tartalmaz. A memóriában minden egyes bájtnak van egy címe is, amely egy szám, amely megadja, hogy a bájt hol található a memóriában. A memória első bájtjának címe 0, a következőé 1, és így tovább. A memória bájtokra való felosztása teszi a memóriát bájtcímezhetővé, mivel minden bájt egyedi címet kap. A bájtmemóriák címei nem használhatók arra, hogy egy bájt egyetlen bitjére utaljanak. A bájt a memória legkisebb címezhető darabja.
Bár egy cím a memória egy adott bájtjára utal, a processzorok lehetővé teszik, hogy a memóriából több bájtot is használjunk egymás után. Ennek a funkciónak a leggyakoribb felhasználása az, hogy egy sorban 2 vagy 4 bájtot használnak egy szám, általában egy egész szám ábrázolására. Néha egyetlen bájtot is használnak egész számok ábrázolására, de mivel ezek csak 8 bit hosszúak, csak 2 8vagy 256 különböző lehetséges értéket tudnak tárolni. Ha 2 vagy 4 bájtot használunk egy sorban, akkor a lehetséges értékek száma 2 16, 65536-ra, illetve 2 32, 4294967296-ra nő.
Amikor egy program egy bájtot vagy több bájtot használ egy sorban valaminek, például egy betűnek, számnak vagy bármi másnak a reprezentálására, ezeket a bájtokat objektumnak nevezzük, mivel mind ugyanannak a dolognak a részei. Annak ellenére, hogy az objektumok mind azonos memóriabájtokban tárolódnak, úgy kezeljük őket, mintha lenne egy "típusuk", amely megmondja, hogyan kell értelmezni a bájtokat: vagy egész számként, vagy karakterként, vagy valamilyen más típusúként (például nem egész értékként). A gépi kódot úgy is felfoghatjuk, mint egy típust, amelyet utasításként értelmeznek. A típus fogalma nagyon-nagyon fontos, mert meghatározza, hogy milyen dolgokat lehet és mit nem lehet tenni az objektummal, és hogyan kell értelmezni az objektum bájtjait. Például nem érvényes negatív számot tárolni egy pozitív szám objektumban, és nem érvényes törtet tárolni egy egész számban.
Egy cím, amely egy több bájtos objektumra mutat (annak a címe), az objektum első bájtjának címe - a legalacsonyabb címmel rendelkező bájt. Mellékesen megjegyezzük, hogy egy fontos dolog, hogy a címéből nem lehet megmondani, hogy milyen típusú egy objektum - vagy akár a mérete -. Sőt, még azt sem lehet megmondani, hogy milyen típusú egy objektum, ha ránézünk. Egy assembly nyelvi programnak nyomon kell követnie, hogy melyik memóriacím milyen objektumokat tartalmaz, és hogy ezek az objektumok mekkorák. Egy program, amely ezt teszi, típusbiztonságos, mert csak olyan dolgokat tesz az objektumokkal, amelyeket a típusuk alapján biztonságosan megtehet. Egy olyan program, amelyik nem teszi ezt, valószínűleg nem fog megfelelően működni. Vegyük észre, hogy a legtöbb program valójában nem tárolja explicit módon, hogy milyen típusú egy objektum, csak következetesen hozzáfér az objektumokhoz - ugyanazt az objektumot mindig ugyanolyan típusúként kezeli.
A processzor
A processzor futtatja (végrehajtja) az utasításokat, amelyeket gépi kódként tárol a főmemóriában. Amellett, hogy a legtöbb processzor képes hozzáférni a memóriához tárolás céljából, a legtöbb processzor rendelkezik néhány kis, gyors, rögzített méretű hellyel az éppen dolgozó objektumok tárolására. Ezeket a helyeket regisztereknek nevezzük. A processzorok általában háromféle utasítástípust hajtanak végre, bár néhány utasítás lehet e típusok kombinációja is. Az alábbiakban néhány példát találunk az egyes típusokra az x86 assembly nyelven.
A memóriát olvasó vagy író utasítások
A következő x86 assembly nyelvi utasítás egy 2 bájtos objektumot olvas be (tölt be) a 4096-os (hexadecimálisan 0x1000) bájtcímről az "ax" nevű 16 bites regiszterbe:
Ebben az assembly nyelvben a szám (vagy regiszternév) körüli szögletes zárójelek azt jelentik, hogy a számot a felhasználandó adatok címeként kell használni. A címnek az adatra való mutatásra való használatát indirekciónak nevezzük. A következő példában a szögletes zárójelek nélkül egy másik regiszter, a bx, valójában a 20-as értéket tölti be.
Mivel nem használtunk indirekciót, maga a tényleges érték került a regiszterbe.
Ha az operandusok (a mnemonikus után következő dolgok) fordított sorrendben jelennek meg, akkor egy olyan utasítás, amely betölt valamit a memóriából, ahelyett, hogy a memóriába írná azt:
Itt az 1000h címen lévő memória az ax értékét kapja. Ha ezt a példát közvetlenül az előző példa után hajtjuk végre, akkor az 1000h és 1001h címeken lévő 2 bájt egy 2 bájtos egész szám lesz 20-as értékkel.
Matematikai vagy logikai műveleteket végrehajtó utasítások
Egyes utasítások olyan dolgokat végeznek, mint a kivonás vagy logikai műveletek, mint a nem:
A cikk korábbi gépi kódos példája ezt jelentené assembly nyelven:
Itt a 42 és az ax értékeket összeadjuk, és az eredményt az ax értékben tároljuk. Az x86-os assemblyben is lehetséges a memória-hozzáférés és a matematikai művelet ilyen módon történő kombinálása:
Ez az utasítás az 1000h-ban tárolt 2 byte-os egész szám értékét hozzáadja az ax-hoz, és a választ az ax-ban tárolja.
Ez az utasítás kiszámítja az ax és bx regiszterek tartalmának vagy értékét, és az eredményt visszatárolja az ax-be.
Olyan utasítások, amelyek eldöntik, hogy mi legyen a következő utasítás.
Az utasítások általában abban a sorrendben kerülnek végrehajtásra, ahogyan a memóriában megjelennek, azaz ahogyan az assembly kódban be vannak írva. A processzor csak egymás után hajtja végre őket. Ahhoz azonban, hogy a processzorok bonyolult dolgokat tudjanak elvégezni, különböző utasításokat kell végrehajtaniuk aszerint, hogy milyen adatokat kaptak. A processzorok azon képességét, hogy különböző utasításokat hajtsanak végre attól függően, hogy mi az eredmény, elágazásnak nevezzük. Azokat az utasításokat, amelyek eldöntik, hogy mi legyen a következő utasítás, elágazási utasításnak nevezzük.
Ebben a példában tegyük fel, hogy valaki ki akarja számolni, mennyi festékre van szüksége egy bizonyos oldalhosszúságú négyzet kifestéséhez. A méretgazdaságosság miatt azonban a festékbolt nem fog kevesebb festéket eladni, mint amennyi egy 100 x 100-as négyzet kifestéséhez szükséges.
Ahhoz, hogy kiszámítsák, mennyi festékre lesz szükségük a festeni kívánt négyzet hossza alapján, ezt a lépéssort találják ki:
- vonjuk ki 100-at az oldalhosszból
- ha a válasz kisebb, mint nulla, állítsuk az oldalhosszúságot 100-ra.
- szorozza meg az oldalhosszúságot önmagával
Ez az algoritmus a következő kóddal fejezhető ki, ahol ax az oldalhossz.
Ez a példa számos új dolgot mutat be, de az első két utasítás ismerős. Az ax értékét bemásolják a bx-be, majd 100-at kivonják a bx-ből.
Az egyik új dolog ebben a példában az úgynevezett címke, amely fogalom általában az assembly nyelvekben is megtalálható. A címkék bármi lehetnek, amit a programozó akar (kivéve, ha egy utasítás neve, ami összezavarná az asszemblert). Ebben a példában a címke a 'continue'. Ezt az assembler egy utasítás címeként értelmezi. Ebben az esetben ez a mult ax címe.
Egy másik új fogalom a zászlóké. Az x86-os processzorokon számos utasítás "zászlókat" állít a processzorban, amelyeket a következő utasítás felhasználhat annak eldöntésére, hogy mit tegyen. Ebben az esetben, ha a bx kisebb volt 100-nál, a sub be fog állítani egy flag-et, amely azt mondja, hogy az eredmény kisebb volt nullánál.
A következő utasítás a jge, ami a 'Jump if Greater than or Equal to' rövidítése. Ez egy elágazási utasítás. Ha a processzorban lévő jelzők azt mutatják, hogy az eredmény nagyobb vagy egyenlő nullánál, akkor a következő utasítás helyett a processzor a folytatás címkéjénél lévő utasításra ugrik, ami a mul ax.
Ez a példa jól működik, de a legtöbb programozó nem ezt írná. A kivonási utasítás helyesen állította be a flaget, de megváltoztatja az értéket is, amin működik, amihez az ax-et a bx-be kellett másolni. A legtöbb assembly nyelv lehetővé teszi az olyan összehasonlító utasításokat, amelyek nem változtatják meg az átadott argumentumokat, de a zászlókat mégis megfelelően állítják be, és ez alól az x86 assembly sem kivétel.
Most ahelyett, hogy kivonjuk ax-ból a 100-at, megnézzük, hogy ez a szám kisebb-e nullánál, és visszaadjuk ax-nak, ax változatlanul marad. A zászlók továbbra is ugyanúgy vannak beállítva, és az ugrás továbbra is ugyanazokban a helyzetekben történik.
Bemenet és kimenet
Bár a bevitel és a kimenet a számítástechnika alapvető része, az assembly nyelven nem egyféleképpen történik. Ez azért van így, mert az I/O működése a számítógép beállításaitól és a rajta futó operációs rendszertől függ, nem csak attól, hogy milyen processzorral rendelkezik. A példa részben a Hello World példa MS-DOS operációs rendszer hívásokat használ, az utána következő példa pedig BIOS hívásokat.
Lehetőség van I/O végrehajtásra assembly nyelven. Valójában az assembly nyelv általában mindent képes kifejezni, amire egy számítógép képes. Azonban annak ellenére, hogy az assembly nyelven vannak olyan utasítások az összeadásra és az elágazásra, amelyek mindig ugyanazt teszik, az assembly nyelven nincsenek olyan utasítások, amelyek mindig I/O-t csinálnak.
Fontos megjegyezni, hogy az I/O működésének módja nem része semmilyen assembly nyelvnek, mivel nem része a processzor működésének.
Assembly nyelvek és hordozhatóság
Bár az assembly nyelvet nem közvetlenül a processzor futtatja - a gépi kódot igen, mégis sok köze van hozzá. Minden processzorcsalád más-más funkciókat, utasításokat, szabályokat támogat, hogy az utasítások mire képesek, és szabályokat arra vonatkozóan, hogy az utasítások milyen kombinációja hol megengedett. Emiatt a különböző típusú processzoroknak még mindig különböző assembly nyelvekre van szükségük.
Mivel az assembly nyelv minden egyes verziója egy processzorcsaládhoz kötött, hiányzik belőle az úgynevezett hordozhatóság. Valami, ami hordozható vagy hordozható, könnyen átvihető egyik számítógéptípusról a másikra. Míg más típusú programozási nyelvek hordozhatóak, az assembly nyelv általában nem az.
Assembly nyelv és magas szintű nyelvek
Bár az assembly nyelv lehetővé teszi a processzor összes funkciójának egyszerű használatát, a modern szoftverprojektekben több okból sem használják:
- Egy egyszerű program assemblyben való kifejezése sok erőfeszítést igényel.
- Bár az assembly nyelv nem olyan hibaérzékeny, mint a gépi kód, mégis nagyon kevés védelmet nyújt a hibák ellen. Szinte az összes assembly nyelv nem biztosítja a típusbiztonságot.
- Az assembly nyelv nem támogatja a jó programozási gyakorlatokat, mint például a modularitást.
- Bár minden egyes assembly nyelvi utasítás könnyen érthető, nehéz megmondani, hogy mi volt a programozó szándéka, aki írta. Valójában egy program assembly nyelve annyira nehezen érthető, hogy a vállalatok nem aggódnak amiatt, hogy az emberek szétszedik (megszerzik az assembly nyelvét) a programjaikat.
E hátrányok miatt a legtöbb projektben magas szintű nyelveket, például Pascal, C és C++ nyelveket használnak. Ezek lehetővé teszik a programozók számára, hogy közvetlenebbül fejezzék ki elképzeléseiket, ahelyett, hogy minden egyes lépésnél meg kelljen mondaniuk a processzornak, hogy mit tegyen. Azért nevezik őket magas szintűnek, mert a programozó által ugyanannyi kódban kifejezhető ötletek bonyolultabbak.
A fordított magas szintű nyelveken kódot író programozók egy fordítónak nevezett programot használnak a kódjuk assembly nyelvre történő átalakításához. A fordítókat sokkal nehezebb megírni, mint az assemblereket. Emellett a magas szintű nyelvek nem mindig teszik lehetővé a programozók számára, hogy a processzor összes funkcióját kihasználják. Ennek oka, hogy a magas szintű nyelveket úgy tervezték, hogy minden processzorcsaládot támogassanak. Az assembly nyelvekkel ellentétben, amelyek csak egy processzortípust támogatnak, a magas szintű nyelvek hordozhatóak.
Bár a fordítók bonyolultabbak, mint az assemblerek, a fordítók készítése és kutatása évtizedek óta nagyon jól működik. Ma már nem sok ok van arra, hogy a legtöbb projekthez assembly nyelvet használjunk, mert a fordítók általában ugyanolyan jól vagy jobban ki tudják találni, hogyan fejezzék ki a programokat assembly nyelven, mint a programozók.
Példa programok
Egy Hello World program x86 Assembly nyelven írva:
Egy függvény, amely egy számot kiír a képernyőre a BIOS megszakítások segítségével, NASM x86 assembly nyelven írva. Moduláris kódot lehet assemblyben is írni, de ez extra erőfeszítést igényel. Vegye figyelembe, hogy minden, ami egy sorban pontosvessző után áll, megjegyzésnek minősül, és az assembler figyelmen kívül hagyja. A kommentek beírása az assembly nyelvű kódba nagyon fontos, mert a nagy assembly nyelvű programokat nagyon nehéz megérteni.
Kérdések és válaszok
K: Mi az az assembly nyelv?
V: Az assembly nyelv egy olyan programozási nyelv, amellyel közvetlenül meg lehet mondani a számítógépnek, hogy mit tegyen. Majdnem pontosan olyan, mint a számítógép által érthető gépi kód, azzal a különbséggel, hogy számok helyett szavakat használ.
K: Hogyan érti meg a számítógép az assembly programot?
V: A számítógép közvetlenül nem igazán képes megérteni egy assembly programot, de könnyen át tudja alakítani a programot gépi kóddá azáltal, hogy a program szavait a számokkal helyettesíti, amelyeket azok jelölnek. Ezt a folyamatot egy asszembler segítségével végzi.
K: Mik az utasítások egy assembly nyelvben?
V: Az utasítások egy assembly nyelvben olyan apró feladatok, amelyeket a számítógép a program futtatása közben végrehajt. Azért hívják őket utasításoknak, mert utasítják a számítógépet, hogy mit tegyen. A számítógépnek azt a részét, amely az utasítások végrehajtásáért felelős, processzornak nevezzük.
K: Milyen típusú programozási nyelv az assembly?
V: Az assembly nyelv alacsony szintű programozási nyelv, ami azt jelenti, hogy csak olyan egyszerű feladatok elvégzésére használható, amelyeket a számítógép közvetlenül megért. Összetettebb feladatok elvégzéséhez minden egyes feladatot külön-külön összetevőkre kell bontani, és minden egyes összetevőhöz külön-külön kell utasításokat adni.
K: Miben különbözik ez a magas szintű nyelvektől?
V: A magas szintű nyelvek tartalmazhatnak egyetlen parancsot, mint például a PRINT "Hello, world!", amely utasítja a számítógépet, hogy automatikusan hajtsa végre az összes ilyen kis feladatot anélkül, hogy külön-külön meg kellene adni őket, mint ahogyan azt egy assembly program esetében tenné. Ezáltal a magas szintű nyelvek könnyebben olvashatók és érthetők az emberek számára, mint a sok egyedi utasításból álló assembly programok.
K: Miért lehet nehéz az emberek számára egy assembly program olvasása?
V: Mert sok egyedi utasítást kell megadni ahhoz, hogy egy összetett feladat, például valaminek a képernyőre történő kinyomtatása vagy számítások elvégzése adathalmazokon - olyan dolgok, amelyek természetes emberi nyelven kifejezve nagyon egyszerűnek és egyszerűnek tűnnek -, így sok kódsor alkothat egy utasítást, ami megnehezíti az olyan emberek számára, akik nem tudják, hogyan működnek a számítógépek belsőleg ilyen alacsony szinten, hogy kövessék és értelmezzék, mi történik bennük.