Глава 3. Памет
Може да се каже, че паметта е най-важната част от компютъра. Тя е която прави възможно съществуването на програми и осмисля съществуването на процесора и целият компютър.
Какво представлява паметта? Най-общо казано, това е устройство, което може да запомня числа и след това да ги възпроизвежда при поискване. Образно можете да си го представите като множество кутийки (клетки) във всяка от които е поставено по едно число. Числата във всяка клетка могат да сменят стойността си, но не може да изчезнат - във всяка клетка винаги има число.
Паметта в съвременните компютри е организирана на байтове - тоест във всяка клетка може да се постави по едно 8 цифрено двоично число (или 2 цифрено шестнайсетично, или изобщо, число от 0 до 255). Надявам се, си спомняте Глава 1 в която се говореше за числа и бройни системи.
Клетките от паметта трябва да могат да се различават една от друга, за да може процесорът да избира точно от коя да прочете числото което му трябва. Затова клетките от паметта са номерирани. Всяка клетка си има т.н. нареченият "адрес" - това е просто номера на клетката по която тя се различава от всички други.
Когато програмата иска да работи с паметта, тя трябва да укаже от кой адрес иска да прочете или да запише число. Ще повторя, защото е важно: В паметта ВИНАГИ има нещо записано. Често чувам начинаещи програмисти да говорят за "празни" клетки от паметта. Питали са ме: "Ами ако няма нищо записано в паметта?" Такова нещо принципно не може да съществува - в паметта винаги има някаква записана стойност. Друг е въпросът дали тази стойност има някакъв смисъл за нас или не.
1. Запис на големи числа в паметта
Терминология: Да уточним още веднъж терминологията. "Младша" се нарича цифра (или групи цифри) които се намират по-надясно в числото. "старши" - цифри, които се намират по-наляво в числото.
Очевидно е, че понятието е относително. Цифра, която се намира в средата, например, ще бъде по-младша от цифрите, които са по-наляво от нея и по-старша от цифрите които са по-надясно от нея.
Колкото по-наляво се намира една цифра (група цифри) - толкова по-старша е тя. Най-лявата цифра се нарича най-старша.
В английската литература се използва термина "LSB" за най-младшият байт. Това е съкращение от "least significant byte" - най-малко значещ байт.
"MSB" е съкращение от "most significant byte" - най-значещ байт.
Например в числото (двойна дума): $12345678 имаме:
$78 - "LSB" - най-младши байт. $12 - "MSB" - най-старши байт. $34 е по-старши байт от $56, а 7 е по-младша цифра от 2.
Надявам се, това би трябвало да изясни терминологията, ако досега не е била ясна.
Както казахме по-горе, в една клетка от паметта се записва един байт. А знаем, че в един байт може да се запише число от 0 до 255.
Програмите обаче трябва да могат да работят и с по-големи числа. Как такива числа се записват в паметта?
Разбира се в няколко байта. Мястото на тези байтове може да е произволно, но като правило големите числа винаги се записват в няколко ПОСЛЕДОВАТЕЛНИ байта. Как точно става това. Нека имаме числото $1234. Това число е четири цифрено (шестнайсетично) и затова се записва в 2 байта (във всеки байт може да се запише до 2 цифрено шестнайсетично число). Ако искаме да запишем това число, да кажем на адреси $1000 и $1001, то тези адреси ще съдържат:
$1000: $34 $1001: $12
Тоест - младшият байт на числото се записва на по-малкият адрес, а старшият байт на по-големият байт от двойката адреси.
(Този метод е възприет от повечето производители на процесори в наше време. Но например в процесорите на Motorola се използва обратният запис - първо старшата и след това младшата част.)
Ако числото трябва да се запише в повече байтове - например 4 (двойна дума) правилото е същото: Първо по-младшите, след това по-старшите байтове.
Ако числото е например: $12345678 - то ще се запише така:
$1000: $78
$1001: $56
$1002: $34
$1003: $12
За по-кратко, горният запис може да се направи така:
$1000: $78, $56, $34, $12
Група от няколко байта в паметта която съдържа едно число, често се нарича променлива. Обикновенно в компютърното програмиране се използват променливи, съставени от брой байтове, който е степен на двойката. Тоест 1, 2, 4, 8, 16 и т.н. байта. Ако групата е по-голяма отколкото трябва, за да се запише даденото число, то числото се попълва с нули от ляво (естествено - само тогава не се променя стойността на числото).
Например, за съхраняване то числото $abcde трябват 2 байта и половина. (2 цифри на байт...) - за да го съхраним по най-естествен за компютъра начин трябва да изберем 4 байтова променлива и в нея да запишем $000abcde по следният начин:
0000: $de, $bc, $0a, $00
2. Запис на текст в паметта
Програмите обикновенно работят много с текстова информация. Това е и текста който се показва на екрана, и текста, който се чете от файлове и се записва в бази данни (и даже този текст, който четете в момента.)
Текстовата информация се записва в паметта във формата на числа. Че как иначе, щом в паметта могат да се записват само числа?
Идеята е следната: На всеки символ, който искаме да използваме, трябва да присвоим някакъв номер. Когато записваме текст в паметта, за всеки символ от текста ще записваме неговият номер. След това, програмата, която отговаря за извеждането на текста на екрана е длъжна да превърне всяко число в съответното (правилно) графично изображение на знака.
Тази операция е толкова рутинна, че винаги е част от операционната система, а понякога се реализира дори и хардуерно.
Начинът на кодиране на символите с числа е стандартизиран и в момента се използват общо взето два стандарта. Първият е т.н. ASCII код.
ASCII е съкращение от American Standard Code for Interchange Information - Американски стандартен код за обмен на информация.
При ASCII кодировката, всеки символ се кодира в един байт. Знаем, че в един байт могат да се кодират максимум 256 символа (0..$ff). Първата половина на този обхват (0..127 ($7f) ) е запълнена със стандартните символи (!@#$%№...), цифри (123455...) и буквите от латинската азбука. Тази половина от 128 символа е напълно стандартизирана. Втората половина е оставена за използване от различните национални азбуки - кирилица, разните букви с точки отгоре (умлаути) за немският например, гръцки букви и т.н. Всяка държава има право да си стандартизира своя втора част на ASCII таблицата.
Проблема възниква, когато поискаме да напишем текст в който да се използват букви от няколко езика или просто искаме да напишем програма, която да може да работи с документи на разни езици. Просто ASCII таблицата е много малка и не може да събере повече от латиница и един допълнителен език...
Затова, напоследък започва да се използва широко т.н. UNICODE. Идеята при уникода е същата, само че за всеки символ се отделя не един байт, а една дума (два байта). В една дума могат да се кодират 65536 символа (0..$ffff) - повече отколкото са нужни за кодирането на всички символи от всички азбуки на света, както и много допълнителни графични символи.
Съдържанието на таблицата UNICODE се стандартизира от "The Unicode Consortium" - oрганизация, която се грижи за дефинирането на таблицата - тоест на кои символи, кои номера да се присвоят. Можете да прочетете подробно за UNICODE на: http://www.unicode.org/
Недостатък на UNICODE е, че таблицата е много голяма - изключително трудно е да се обработва напълно - обикновенно се работи с малка част от нея, която ни засяга в момента.
По-нататък в тези уроци ще работим само с ASCII кодировка. Работата с UNICODE, определено не е за начинаещи.
Ето и кратко описание на ASCII таблицата, която се използва за кодиране на кирилица във Windows:
$00 .. $1f: Управляващи символи за принтери и конзоли.
Тези символи не се виждат на екрана, а служат
за командване на поведението на принтери, както
и на начинът по който се форматира текста,
когато се показва на екрана. В частност тук са
символите за нов ред: CR ( $0d ) и за нова
линия: LF( $0а ). Във Windows всяко преминаване
на нов ред се кодира с двата символа един след
друг: $0d, $0a (13, 10). Причината за това е
историческа и се дължи на факта, че принтерите
при получаване на CR ($0d), преместват печатащата
глава крайно в ляво без да преместят хартията, а
при получаване на LF ($0а) преместват хартията с
един ред, без да местят главата. Тоест ако искаме
да отидем в началото на следващият ред, трябва да
изпратим последователно двата символа. Тук няма
да разглеждаме останалите символи от тази група.
$20 - Интервал " "
$21..$2f: ! " # $ % & ' ( ) * + , - . /
$30..$3f: 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
$40..$4f: @ A B C D E F G H I J K L M N O
$50..$5F: P Q R S T U V W X Y Z [ \ ] ^ _
$60..$6F: ` a b c d e f g h i j k l m n o
$70..$7e: p q r s t u v w x y z { | } ~
Следващата част на таблицата съдържа националните символи. Аз я виждам като кирилица, но вие може да я виждате и по друг начин, в зависимост от кодировката на браузърът ви и от шрифта, който се използва.
$80..$8f: Ђ Ѓ ‚ ѓ „ … † ‡ € ‰ Љ ‹ Њ Ќ Ћ Џ
$90..$9f: ђ ‘ ’ “ ” • – — ™ љ › њ ќ ћ џ
$a0..$af: Ў ў Ј ¤ Ґ ¦ § Ё © Є « ¬ ® Ї
$b0..$bf: ° ± І і ґ µ ¶ · ё № є » ј Ѕ ѕ ї
$c0..$cf: А Б В Г Д Е Ж З И Й К Л М Н О П
$d0..$df: Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я
$e0..$ef: а б в г д е ж з и й к л м н о п
$f0..$ff: р с т у ф х ц ч ш щ ъ ы ь э ю я
3. Дефиниране на променливи и данни в асемблер
Асемблерът има някои директиви, които ни дават възможност да дефинираме в програмата променливи с различни дължини, както и да им задаваме някаква начална стойност. Има две групи такива директиви - за дефиниране на инициализирани данни - тоест памет, която се запълва с определени от нас числа, още по време на компилацията, и за дефиниране на неопределени данни - тоест за тези данни се отделят клетки от паметта, без те да се запълват със зададени от нас данни.
Най-безопасно е да се смята, че такива клетки са запълнени в началото на работата на програмата със случайни числа. Предимството на този тип данни е, че в повечето изпълними файлови формати, неопределените данни не се записват във файла, а се заделят от паметта в момента на стартирането. Това може да намали размера на програмата драстично.
1. Директиви за дефиниране на инициализирани данни:
db - define byte - дефинира байтове.
dw - define word - дефинира думи (2 байта)
dd - define double word - дефинира двойни думи (4 байта)
dq - define quadruple word - дефинира четворни думи (8 байта)
Синтаксисът на всички е приблизително един и същ:
етикет dd арг1, арг2, арг3...
"етикет" е символното име на данните (по-точно на групата данни дефинирани до следващият етикет). Не е задължителен. Това е всъщност адреса на данните. Тъй като по времето когато пишем програмата, ние не знаем къде точно ще попаднат в паметта тези данни, вместо да използваме някакво число за адрес, използваме символно име. По време на компилацията, асемблера ще изчисли точният адрес и навсякъде в програмата, където сме използвали символното име, ще го замести с числовата му стойност.
dd (или db, dw, dq) е съответната директива, която казва на асемблера колко байта да отделя за всяко дефинирано число.
арг1, арг2, т.н. са собствено числата, които искаме да се запишат в паметта.
Примери:
db 1, 2, 3, 4
ще формира последователността от байтове: $01, $02, $03, $04
dw 1, 2, 3, 4
ще се компилира до: $01, $00, $02, $00, $03, $00, $04, 00 (иначе казано, до последователността от думи: $0001, $0002, $0003, $0004
При задаване на данните могат да се използва и текст затворен в единични или двойни кавички. Ако директивата е "db" текста може да е произволно дълъг - всеки символ от него ще заеме един байт. Ако директивата е някоя друга, дължината на текста в кавичките може да бъде максимум, колкото е дължината на съответната променлива: 2 символа за дума, 4 символа за двойна дума и 8 символа за четворна дума. При по-голяма дължина, компилатора ще даде грешка при компилацията.
Пример:
db 'I am ASM coder', 1, 2, 3, 4
ще дефинира в паметта поредицата от байтове:
xxxx: $49 $20 $61 $6d $20 $41 $53 $4d $20
$63 $6f $64 $65 $72 $01 $02 $03 $04
2. Директиви за дефиниранe на неинициализирани данни.
Това са директивите: rb, rw, rd, rq. Мнемониката е от: "reserve byte (word, dword, qword). Синтаксисът на тези директиви е:
етикет rb брой
Тук: "етикет" е отново незадължителен етикет. А брой е число, което показва колко елемента желаете да се запазят за данни. Тъй като това са неинициализирани данни, не можем да указваме какво да съдържат. Такива данни задължително трябва да се инициализират (да се запише нещо в тях) преди използване в програмата, защото в началото могат да съдържат произволни числа.
Пример:
rd 100 ; запазва за данни 400 байта (100 двойни думи)
rb 32 ; запазва 32 байта.
rq 4 ; запазва 32 байта (4 четворни думи по 8 байта всяка)
Тайна: Накрая една малка тайна. Windows винаги инициализира "неинициализираните" данни с 0, при стартиране на програмата. Не ви съветвам да използвате това в програмите си обаче, тъй като е недокументирано и в някоя от бъдещите версии може да бъде променено, така че програмите ще спрат да работят правилно.
Все пак в някои ситуации може да бъде полезно...
В следващата глава ще разгледаме отново нашата най-проста програма и ще видим как, използвайки огромните възможности на FASM да я напишем само в няколко реда...
Last modified on: 06.06.2012 21:41:24