Асемблерен език | език за програмиране
Езикът за асемблиране е език за програмиране, който може да се използва, за да се каже директно на компютъра какво да прави. Езикът за асемблиране е почти точно като машинния код, който компютърът разбира, с изключение на това, че използва думи вместо числа. Компютърът всъщност не може да разбере директно програма на асемблер. Той обаче може лесно да промени програмата в машинен код, като замени думите на програмата с числата, които те означават. Програмата, която прави това, се нарича асемблер.
Програмите, написани на асемблер, обикновено се състоят от инструкции, които представляват малки задачи, които компютърът изпълнява, когато изпълнява програмата. Те се наричат инструкции, защото програмистът ги използва, за да инструктира компютъра какво да прави. Частта от компютъра, която изпълнява инструкциите, е процесорът.
Асемблерният език на компютъра е език от ниско ниво, което означава, че може да се използва само за изпълнение на прости задачи, които компютърът разбира директно. За да се изпълняват по-сложни задачи, трябва да се каже на компютъра всяка от простите задачи, които са част от сложната задача. Например компютърът не разбира как да отпечата едно изречение на екрана си. Вместо това програма, написана на асемблер, трябва да му каже как да направи всички малки стъпки, които участват в отпечатването на изречението.
Такава асемблерна програма ще се състои от много, много инструкции, които заедно правят нещо, което изглежда много просто и елементарно за човека. Това затруднява четенето на асемблерната програма от хората. За разлика от това, един език за програмиране от високо ниво може да съдържа една-единствена инструкция, например PRINT "Hello, world!", която ще каже на компютъра да изпълни всички малки задачи вместо вас.
Разработване на езика за асемблиране
Когато компютърните специалисти за първи път създадоха програмируеми машини, те ги програмираха директно с машинен код, който представлява поредица от числа, указващи на компютъра какво да прави. Писането на машинен език е било много трудно и е отнемало много време, така че в крайна сметка е бил създаден асемблерният език. Езикът на асемблера е по-лесен за четене от човек и може да бъде написан по-бързо, но все още е много по-труден за използване от човек, отколкото език за програмиране от високо ниво, който се опитва да имитира човешкия език.
Програмиране в машинен код
За да програмира на машинен код, програмистът трябва да знае как изглежда всяка инструкция в двоичен (или шестнадесетичен) код. Въпреки че за компютъра е лесно бързо да разбере какво означава машинният код, за програмиста това е трудно. Всяка инструкция може да има няколко форми, всички от които изглеждат просто като куп числа за хората. Всяка грешка, която някой допусне, докато пише машинен код, ще бъде забелязана едва когато компютърът направи грешното нещо. Откриването на грешката е трудно, защото повечето хора не могат да разберат какво означава машинният код, като го погледнат. Пример за това как изглежда машинният код:
05 2A 00
Този шестнадесетичен машинен код казва на процесора на компютър x86 да добави 42 към акумулатора. Много е трудно човек да го прочете и разбере, дори и да знае машинния код.
Използване на асемблер вместо това
На езика за асемблиране всяка инструкция може да бъде записана като кратка дума, наречена мнемонична, последвана от други неща като числа или други кратки думи. Мнемониката се използва, за да не се налага програмистът да запомня точните цифри в машинния код, необходими, за да каже на компютъра да направи нещо. Примери за мнемоника в езика на асемблер са add, който добавя данни, и mov, който премества данни от едно място на друго. Тъй като "мнемоника" е необичайна дума, вместо нея понякога се използва, често неправилно, изразът тип инструкция или просто инструкция. Думите и числата след първата дума дават повече информация за това какво да се направи. Например нещата след add може да са какви две неща да съберем, а нещата след mov казват какво да преместим и къде да го поставим.
Например машинният код от предишния раздел (05 2A 00) може да бъде записан на асемблер като:
добавете ax,42
Езикът за асемблиране също така позволява на програмистите да записват по по-лесен начин действителните данни, които програмата използва. Повечето езици за асемблиране имат поддръжка за лесно създаване на числа и текст. В машинния код всеки различен тип число, като положително, отрицателно или десетично, би трябвало да се преобразува ръчно в двоичен код, а текстът би трябвало да се дефинира една по една букви като числа.
Езикът за асемблиране представлява т.нар. абстракция на машинния код. Когато се използва асемблер, не е необходимо програмистите да знаят подробности за значението на числата за компютъра, асемблерът решава това. Езикът за асемблиране всъщност все още позволява на програмиста да използва всички функции на процесора, които може да използва с машинен код. В този смисъл езикът за асемблиране притежава една много добра и рядка черта: той има същата способност да изразява неща като това, от което се абстрахира (машинния код), като същевременно е много по-лесен за използване. Поради тази причина машинният код почти никога не се използва като език за програмиране.
Разглобяване и отстраняване на грешки
Когато програмите са готови, те вече са трансформирани в машинен код, така че процесорът да може да ги изпълнява. Понякога обаче, ако в програмата има грешка (бъг), програмистите искат да могат да кажат какво прави всяка част от машинния код. Дезасемблерите са програми, които помагат на програмистите да направят това, като трансформират машинния код на програмата обратно в асемблер, който е много по-лесен за разбиране. Дезасемблерите, които превръщат машинния код в език на асемблер, правят обратното на асемблерите, които превръщат езика на асемблер в машинен код.
Компютърна организация
За да се разбере как работи една програма на асемблер, е необходимо разбиране за това как са организирани компютрите, как изглеждат на много ниско ниво. На най-опростено ниво компютрите имат три основни части:
- основна памет или RAM, в която се съхраняват данни и инструкции,
- процесор, който обработва данните чрез изпълнение на инструкциите, и
- вход и изход (понякога съкращавани като вход/изход), които позволяват на компютъра да комуникира с външния свят и да съхранява данни извън основната памет, за да може по-късно да ги получи обратно.
Основна памет
В повечето компютри паметта е разделена на байтове. Всеки байт съдържа 8 бита. Всеки байт в паметта има и адрес, който представлява число, указващо къде се намира байтът в паметта. Първият байт в паметта има адрес 0, следващият има адрес 1 и т.н. Разделянето на паметта на байтове я прави адресируема, тъй като всеки байт получава уникален адрес. Адресите на байтовите памети не могат да се използват за обозначаване на отделен бит от байт. Байтът е най-малката част от паметта, която може да бъде адресирана.
Въпреки че адресът се отнася до конкретен байт в паметта, процесорите позволяват използването на няколко байта памет подред. Най-честата употреба на тази функция е да се използват 2 или 4 байта в ред за представяне на число, обикновено цяло число. Понякога за представяне на цели числа се използват и единични байтове, но тъй като те са дълги само 8 бита, могат да поберат само 28 или 256 различни възможни стойности. Използването на 2 или 4 байта в ред увеличава броя на различните възможни стойности съответно на 216 , 65536 или 232 , 4294967296.
Когато една програма използва байт или няколко байта в ред, за да представи нещо като буква, число или нещо друго, тези байтове се наричат обект, защото всички те са част от едно и също нещо. Въпреки че всички обекти се съхраняват в идентични байтове в паметта, те се третират така, сякаш имат "тип", който казва как байтовете трябва да се разбират: като цяло число, символ или някакъв друг тип (като нецелочислена стойност). Машинният код също може да се разглежда като тип, който се интерпретира като инструкции. Понятието за тип е много, много важно, защото то определя какви неща могат и не могат да се правят с обекта и как да се интерпретират байтовете на обекта. Например, не е валидно да се съхранява отрицателно число в обект с положително число и не е валидно да се съхранява дроб в цяло число.
Адрес, който сочи към (е адрес на) многобайтови обекти, е адресът на първия байт на този обект - байта, който има най-нисък адрес. Встрани от това е важно да се отбележи, че по адреса на обекта не може да се определи неговият тип или дори размер. Всъщност дори не можете да определите какъв е типът на даден обект, като го погледнете. Програмата на асемблер трябва да следи кои адреси на паметта съдържат кои обекти и колко големи са тези обекти. Програма, която прави това, е типово безопасна, защото прави с обектите само неща, които са безопасни за техния тип. Програма, която не го прави, вероятно няма да работи правилно. Обърнете внимание, че повечето програми всъщност не съхраняват изрично какъв е типът на даден обект, а просто осъществяват последователен достъп до обектите - един и същ обект винаги се третира като обект от един и същи тип.
Процесорът
Процесорът изпълнява инструкции, които се съхраняват като машинен код в основната памет. Освен че имат достъп до паметта за съхранение, повечето процесори разполагат с няколко малки, бързи пространства с фиксиран размер за съхранение на обектите, с които се работи в момента. Тези пространства се наричат регистри. Процесорите обикновено изпълняват три вида инструкции, въпреки че някои инструкции могат да бъдат комбинация от тези видове. По-долу са дадени някои примери за всеки тип на езика за асемблиране x86.
Инструкции, които четат или записват в паметта
Следната инструкция на езика за асемблиране x86 чете (зарежда) 2-байтови обекти от байта на адрес 4096 (0x1000 в шестнадесетична система) в 16-битов регистър, наречен "ax":
mov ax, [1000h]
В този език за асемблиране квадратните скоби около число (или име на регистър) означават, че числото трябва да се използва като адрес на данните, които трябва да се използват. Използването на адрес за насочване към данни се нарича индирекция. В този следващ пример, без квадратните скоби, друг регистър, bx, всъщност получава стойността 20, която е заредена в него.
mov bx, 20
Тъй като не е използвано насочване, в регистъра е въведена самата действителна стойност.
Ако операндите (нещата, които идват след мнемониката) се появят в обратен ред, инструкция, която зарежда нещо от паметта, вместо да го запише в паметта:
mov [1000h], bx
Тук паметта с адрес 1000h получава стойността на bx. Ако този пример се изпълни веднага след предишния, 2-те байта на адреси 1000h и 1001h ще бъдат 2-байтово цяло число със стойност 20.
Инструкции, които изпълняват математически или логически операции.
Някои инструкции извършват неща като изваждане или логически операции като не:
Примерът с машинния код, даден по-рано в тази статия, би бил този на езика за асемблиране:
добавяне на ос, 42
Тук 42 и ax се събират и резултатът се записва обратно в ax. В асемблер x86 също е възможно да се комбинира достъп до паметта и математическа операция по този начин:
добавяне на ax, [1000h]
Тази инструкция добавя стойността на 2-байтовото цяло число, записано в 1000h, към ax и записва отговора в ax.
или ax, bx
Тази инструкция изчислява or на съдържанието на регистрите ax и bx и записва резултата обратно в ax.
Инструкции, които решават каква ще бъде следващата инструкция
Обикновено инструкциите се изпълняват в реда, в който се появяват в паметта, т.е. в реда, в който са въведени в асемблерния код. Процесорът просто ги изпълнява една след друга. За да могат обаче процесорите да правят сложни неща, те трябва да изпълняват различни инструкции в зависимост от това какви са данните, които са им предоставени. Способността на процесорите да изпълняват различни инструкции в зависимост от резултата от нещо се нарича разклоняване. Инструкциите, които решават каква да бъде следващата инструкция, се наричат инструкции за разклоняване.
В този пример предполагаме, че някой иска да изчисли количеството боя, което ще му е необходимо, за да боядиса квадрат с определена дължина на страната. Поради икономии от мащаба обаче магазинът за бои няма да му продаде по-малко от количеството боя, необходимо за боядисване на квадрат с размери 100 x 100.
За да определят количеството боя, което ще им е необходимо, в зависимост от дължината на квадрата, който искат да боядисат, те измислят този набор от стъпки:
- да извадите 100 от дължината на страната
- ако отговорът е по-малък от нула, задайте дължина на страната 100
- умножете дължината на страната по себе си
Този алгоритъм може да се изрази със следния код, където ax е дължината на страната.
mov bx, ax sub bx, 100 jge continue mov ax, 100 continue: mul ax
Този пример въвежда няколко нови неща, но първите две инструкции са познати. Те копират стойността на ax в bx и след това изваждат 100 от bx.
Едно от новите неща в този пример се нарича етикет - концепция, която се среща в езиците за асемблиране като цяло. Етикетите могат да бъдат всичко, което програмистът иска (освен ако не са име на инструкция, което би объркало асемблера). В този пример етикетът е "continue". Той се интерпретира от асемблера като адрес на инструкция. В този случай това е адресът на mult ax.
Друга нова концепция е тази за флаговете. При процесорите x86 много инструкции задават "флагове" в процесора, които могат да се използват от следващата инструкция, за да се реши какво да се направи. В този случай, ако bx е по-малко от 100, sub ще зададе флаг, който казва, че резултатът е по-малък от нула.
Следващата инструкция е jge, което е съкращение от "jump if greater than or equal to". Това е инструкция за разклонение. Ако флаговете в процесора указват, че резултатът е по-голям или равен на нула, вместо да премине към следващата инструкция, процесорът ще скочи към инструкцията с етикет за продължаване, която е mul ax.
Този пример работи добре, но не е това, което повечето програмисти биха написали. Инструкцията за изваждане задава правилно флага, но също така променя стойността, с която оперира, което изисква копиране на ax в bx. Повечето езици за асемблиране позволяват да се използват инструкции за сравнение, които не променят нито един от подадените им аргументи, но въпреки това задават правилно флаговете и асемблито x86 не прави изключение.
cmp ax, 100 jge continue mov ax, 100 continue: mul ax
Сега вместо да извадите 100 от ax, да проверите дали това число е по-малко от нула и да го присвоите обратно на ax, ax остава непроменено. Флаговете все още се задават по същия начин и скокът все още се извършва в същите ситуации.
Вход и изход
Макар че входът и изходът са основна част от компютрите, няма един-единствен начин, по който те да се извършват на езика за асемблиране. Това е така, защото начинът, по който работят входно-изходните операции, зависи от конфигурацията на компютъра и операционната система, с която работи, а не само от вида на процесора. В раздела с примери по-долу примерът Hello World използва извиквания на операционната система MS-DOS, а примерът след него - извиквания на BIOS.
Възможно е да извършвате входно/изходни операции на езика за асемблиране. Всъщност езикът за асемблиране може да изразява всичко, което компютърът е способен да прави. Въпреки това, въпреки че на езика на асемблер има инструкции за добавяне и разклоняване, които винаги правят едно и също нещо, на езика на асемблер няма инструкции, които винаги да правят вход/изход.
Важното е да се отбележи, че начинът, по който работят входно-изходните операции, не е част от езика за асемблиране, тъй като не е част от начина, по който работи процесорът.
Асемблерни езици и преносимост
Въпреки че езикът за асемблиране не се изпълнява директно от процесора, а само от машинния код, той все пак има много общо с него. Всяко семейство процесори поддържа различни функции, инструкции, правила за това какво могат да правят инструкциите и правила за това каква комбинация от инструкции е разрешена. Поради тази причина различните видове процесори все още се нуждаят от различни езици за асемблиране.
Тъй като всяка версия на езика за асемблиране е обвързана с дадена фамилия процесори, липсва нещо, наречено преносимост. Нещо, което има преносимост или е преносимо, може лесно да бъде прехвърлено от един тип компютър на друг. Докато други видове езици за програмиране са преносими, асемблерният език като цяло не е.
Език за асемблиране и езици от високо ниво
Въпреки че езикът за асемблиране позволява лесно да се използват всички функции на процесора, той не се използва в съвременните софтуерни проекти по няколко причини:
- Изразяването на една проста програма на асемблер изисква много усилия.
- Въпреки че не е толкова податлив на грешки, колкото машинният код, езикът за асемблиране все още предлага много слаба защита срещу грешки. Почти всички езици за асемблиране не осигуряват безопасност на типовете.
- Езикът за асемблиране не насърчава добри практики за програмиране като модулност.
- Макар че всяка отделна инструкция на езика за асемблиране е лесна за разбиране, трудно е да се определи какъв е бил замисълът на програмиста, който я е написал. Всъщност езикът на асемблиране на дадена програма е толкова труден за разбиране, че компаниите не се притесняват от хора, които разглобяват (получават езика на асемблиране на) техните програми.
В резултат на тези недостатъци в повечето проекти се използват езици от високо ниво като Pascal, C и C++. Те позволяват на програмистите да изразяват идеите си по-директно, вместо да се притесняват да казват на процесора какво да прави на всяка крачка. Наричат се езици от високо ниво, защото идеите, които програмистът може да изрази в същото количество код, са по-сложни.
Програмистите, които пишат код на компилирани езици от високо ниво, използват програма, наречена компилатор, за да трансформират кода си в език за асемблиране. Компилаторите са много по-трудни за писане, отколкото асемблерите. Освен това езиците от високо ниво не винаги позволяват на програмистите да използват всички функции на процесора. Това е така, защото езиците от високо ниво са проектирани така, че да поддържат всички фамилии процесори. За разлика от езиците за асемблиране, които поддържат само един тип процесор, езиците от високо ниво са преносими.
Въпреки че компилаторите са по-сложни от асемблерите, десетилетията на създаване и изследване на компилатори са ги направили много добри. Сега вече няма много причини да се използва асемблер за повечето проекти, защото компилаторите обикновено могат да разберат как да изразят програмите на асемблер толкова добре или по-добре от програмистите.
Примерни програми
Програма Hello, world!, написана на асемблер x86:
adosseg .model small .stack 100h .data hello_message db 'Hello, World!',0dh,0ah,'$' .code main proc mov ax,@data mov ds,ax mov ah,9 mov dx,offset hello_message int 21h mov ax,4C00h int 21h main endp end main.
Функция, която отпечатва число на екрана, като използва прекъсванията на BIOS, написана на асемблер NASM x86. Възможно е да се напише модулен код на асемблер, но това изисква допълнителни усилия. Обърнете внимание, че всичко, което идва след точка и запетая на даден ред, е коментар и се игнорира от асемблера. Поставянето на коментари в кода на асемблер е много важно, защото големите програми на асемблер са много трудни за разбиране.
; void printn(int number, int base); printn: push bp mov bp, sp push ax push bx push cx push dx push si mov si, 0 mov ax, [bp + 4] ; number mov cx, [bp + 6] ; base gloop: inc si ; length of string mov dx, 0 ; zero dx div cx ; divide by base cmp dx, 10 ; is it ge 10? jge num add dx, '0' ; добавете нула към dx jmp anum num: add dx, ('A'- 10) ; шестнадесетична стойност, добавете 'A' към dx - 10. anum: push dx ; поставете dx на стека. cmp ax, 0 ; трябва ли да продължим? jne gloop mov bx, 7h ; за прекъсване tloop: pop ax ; вземете стойността му mov ah, 0eh ; за прекъсване int 10h ; запишете символа dec si ; отървете се от символа jnz tloop pop si pop dx pop cx pop bx pop ax pop bp ret 4
Книги
- Майкъл Сингър, PDP-11. Програмиране на асемблерен език и машинна организация, John Wiley & Sons, NY: 1980.
- Peter Norton, John Socha, Peter Norton's Assembly Language Book for the IBM PC, Brady Books, NY: 1986.
- Доминик Суитман: Вижте MIPS Run. Издателство Morgan Kaufmann, 1999 г. ISBN 1-55860-410-3
- Джон Уолдрон: Увод в програмирането на асемблерния език на RISC. Addison Wesley, 1998 г. ISBN 0-201-39828-1
- Джеф Дантеман: Асемблерният език стъпка по стъпка. Уайли, 2000 г. ISBN 0-471-37523-3
- Пол Картър: Език за асемблиране на персонални компютри. Безплатна електронна книга, 2001 г.
Уебсайт - Робърт Бритън: Програмиране на асемблерния език на MIPS. Prentice Hall, 2003 г. ISBN 0-13-142044-5
- Рандал Хайд: Изкуството на езика за асемблиране. No Starch Press, 2003 г. ISBN 1-886411-97-2
Работни версии са достъпни онлайн Архивирани 2011-01-28 в Wayback Machine като PDF и HTML - Джонатан Бартлет: Програмиране от нулата. Издателство Бартлет, 2004 г. ISBN 0-9752838-4-7
Наличен онлайн като PDF и като HTML - ASM Community Book "Онлайн книга, пълна с полезна информация за ASM, уроци и примери за код" от ASM Community
Софтуер
- MenuetOS - Операционна система, написана изцяло на 64-битов език за асемблиране
- SB-Assembler за повечето 8-битови процесори/контролери
- GNU lightning - библиотека, която генерира код на асемблер по време на изпълнение и е полезна за компилаторите Just-In-Time
- WinAsm Studio, The Assembly IDE - Безплатни изтегляния, Изходен код , безплатен IDE за асемблиране, много програми с отворен код за изтегляне и популярен съвет Архивирано 2008-08-05 в Wayback Machine
- Асемблерът в мрежата
- GoAsm - безплатен компонент "Go" инструменти: поддръжка на 32-битово и 64-битово програмиране за Windows
Въпроси и отговори
В: Какво представлява езикът за асемблиране?
О: Езикът за асемблиране е език за програмиране, който може да се използва, за да се каже директно на компютъра какво да прави. Той почти напълно прилича на машинния код, който компютърът може да разбере, с изключение на това, че използва думи вместо числа.
В: Как компютърът разбира програмата на асемблер?
О: Компютърът не може да разбере директно програмата на асемблер, но може лесно да промени програмата в машинен код, като замени думите в програмата с числата, които те означават. Този процес се извършва с помощта на асемблер.
В: Какво представляват инструкциите в езика за асемблиране?
О: Инструкциите в езика за асемблиране са малки задачи, които компютърът изпълнява, когато изпълнява програмата. Те се наричат инструкции, защото дават указания на компютъра какво да прави. Частта от компютъра, която отговаря за изпълнението на тези инструкции, се нарича процесор.
Въпрос: Какъв тип език за програмиране е асемблерът?
О: Езикът за асемблиране е език за програмиране от ниско ниво, което означава, че може да се използва само за изпълнение на прости задачи, които компютърът може да разбере директно. За да се изпълняват по-сложни задачи, трябва да се разбие всяка задача на отделни компоненти и да се предоставят инструкции за всеки компонент поотделно.
Въпрос: По какво се различава това от езиците от високо ниво?
О: Езиците от високо ниво могат да имат единични команди, като например PRINT "Hello, world!", които ще кажат на компютъра да изпълни всички тези малки задачи автоматично, без да е необходимо да ги посочвате поотделно, както би трябвало да направите с програма на асемблер. Това прави езиците от високо ниво по-лесни за четене и разбиране от хората, отколкото програмите на асемблер, съставени от много отделни инструкции.
Въпрос: Защо за хората може да е трудно да прочетат програма на асемблер?
О: Защото за изпълнението на сложна задача, като например отпечатване на нещо на екрана или извършване на изчисления върху масиви от данни - неща, които изглеждат много елементарни и прости, когато са изразени на естествен човешки език, - трябва да се зададат много отделни инструкции, които да съставят една инструкция, което затруднява хората, които не знаят как работят компютрите вътрешно на такова ниско ниво, да ги следят и да тълкуват какво се случва в тях.