Убрать дубли в sql запросе. Удаление повторений в T-SQL

Убрать дубли в sql запросе. Удаление повторений в T-SQL

20.03.2019

высокого уровня

Несмотря на огромные возможности языков высокого уровня, иногда возникает необходимость применения Ассемблера в программах на ЯВУ. Наиболее распространены два подхода:

1) в тексте программы, написанной на языке высокого уровня, делаются ассемблерные вставки на встроенном ассемблере;

2) подключаются внешние ассемблерные модули: файл с процедурами пишется на внешнем ассемблере, компилируется в объектный файл OBJ, который подключается к проекту на ЯВУ.

Ассемблерные вставки применяются:

Для повышения быстродействия программы. Хотя большинство компиляторов делают оптимизацию кода, некоторым задачам даже этого мало. Тогда участки кода, критичные ко времени, пишут на ассемблере.

Для вызова команд, не используемых компилятором ЯВУ. С появлением новых поколений процессоров добавляются новые, более эффективные, машинные команды. Однако компиляторы ЯВУ при переводе программы с языка высокого уровня в машинные коды стараются использовать устаревшие команды i386-го, чтобы обеспечить максимальную совместимость со всеми, даже старыми, компьютерами. А если нужны специализированные команды новых процессоров (MMX, XMM, SSE, SSE II), приходится писать на ассемблере.

Область применения ассемблерных вставок ограничена возможностями компилятора. Например, во встроенном ассемблере запрещается вызывать некоторые привилегированные команды; «неизвестные» компилятору команды, появившиеся уже после его выпуска. В то время как использование внешнего ассемблера не имеет ограничений.

3.3. Встроенный ассемблер

Системы программирования Delphi, C++ Builder, Visual C++ позволяют вставлять в текст программы на языке высокого уровня участки кода, написанные на ассемблере. Этот ассемблер называется встроенным, и имеет незначительные синтаксические отличия от ассемблера TASM или MASM. Подробнее про него можно прочитать в справочной системе ЯВУ. Здесь приводятся краткие сведения.

    C ++ Builder, Visual C++ :

Ассемблерный текст заключается в блок _asm{…}

cmp eax, SomeVariable

В ассемблерных вставках можно использовать ранее объявленные переменные, функции и другие идентификаторы.

    Delphi :

Ассемблерные команды заключаются между словами asm end ;

cmp eax, SomeVariable

Внутри ассемблерного блока можно обращаться по именам к переменным, функциям, процедурам и меткам. Переменные, объявленные внутри блока директивами DB, DW и т.п. будут размещены в сегменте кода, а не данных. Это нужно учесть, чтобы компилятор не стал исполнять их значения как машинные коды – это может привести к ошибке исполнения программы.

Если внутри ассемблерного блока нужны переходы по меткам, их делают локальными – имена таких меток следует начинать с символа @. Область действия локальной метки ограничена ассемблерным блоком:

test SomeVariable , 0Fh

... // какие-то команды

@M1: // это локальная метка

3.4. Подключение внешних ассемблерных модулей в программы на языках высокого уровня

Этап 1 . Создание ассемблерного файла с экспортируемой процедурой. Чтобы подключение ассемблерного модуля к программе на ЯВУ было корректным, он должен удовлетворять правилам, в соответствии с которыми создает программу компилятор ЯВУ. Так, должны совпадать имена сегментов, конвенции вызова и т.д. Каркас ассемблерного модуля для подключения к Delphi, C++ Builder и Visual C++, приведен в листинге 1.

Листинг 1 – Каркас ассемблерного модуля для подключения к программам Win32.

.486 ; 32-разрядные приложения

. model flat ; в программах Win32 используется линейная модель

; памяти (flat)

. bss

; в этом сегменте описываются неинициализированные данные

. const

; в этом сегменте описываются типизированные константы

. data

; в этом сегменте описываются переменные с начальными значениями

. code

PUBLIC Имя_процедуры ; чтобы процедуру можно было вызывать из

; программы на ЯВУ, её нужно объявить экспортируемой

; реализация процедуры

Имя_процедуры proc near ; все процедуры – ближние

push ebp ; если процедура с параметрами, то в начале процедуры

; регистр

mov ebp , esp ; затем установить ebp = esp для обращения к параметрам

; здесь тело процедуры

pop ebp ; в конце восстанавливаем ebp

ret N ; если процедура освобождает стек из-под параметров сама, то

; N – число байтов, которое занимает стековый кадр,

; N всегда кратно 4.

Имя_процедуры endp

end ; конец модуля

Директива PUBLIC применяется, чтобы сделать имя процедуры «видимой» за пределами модуля, чтобы её «нашел» компилятор ЯВУ.

Компиляторы С++ различают в именах функций большие и маленькие буквы. Delphi, обычно не чувствительная к регистру, при импорте процедур из внешних OBJ и DLL файлов, буквы большие и малые различает. Поэтому при объявлении процедуры в ассемблерном модуле её имя следует писать так, как оно будет вызываться в ЯВУ.

Замечания по написанию тела процедуры – в п.3.5.

Этап 2 . Компиляция ассемблерного модуля в файл OBJ.

При компиляции следует использовать ключ /ml , чтобы заглавные и строчные буквы различались:

TASM.EXE /ml имя_файла .asm

Если этого не сделать, то процедуру MyProc язык высокого уровня увидит как MYPROC , поскольку TASM по умолчанию все экспортируемые имена записывает заглавными буквами.

Этап 3 . Подключение объектного файла к программе на ЯВУ.

!!! Предварительно объектный файл скопируйте в папку с проектом программы на ЯВУ.

    C ++ Builder :

Объектный модуль можно подключить тремя способами:

1) добавить к проекту через меню Project / Add To Project, тип файлов "OBJ".

2) директивой

#pragma link "имя_файла.obj"

3) директивой

USEOBJ("имя_файла.obj")

    Visual C++

В VC++ 6.0 имя OBJ-файла нужно вписать в параметры командной строки компоновщика: для этого выбрать меню Project / Settings, открыть закладку Link и вписать имя OBJ-файла в параметры командной строки “Object/Library modules”.

В VC++.NET выбирается меню Project/Properties, в дереве свойств папка C/C++ → Command Line и имя OBJ-файла вписывается в строку “Addition options”.

    Delphi :

Для подключения объектного файла в начало программы (между строками Program … или Unit … и строкой Uses …) добавляется директива:

{$LINK имя_файла.obj}

или {$L имя_файла.obj}

Этап 4 . Объявление в программе импортируемой процедуры.

С помощью описанных ниже директив имя ассемблерной процедуры становится видимым в программе на ЯВУ, и её можно далее вызывать, как обычную процедуру или функцию.

    C ++ Builder , Visual C++ :

Заголовок (прототип) ассемблерной функции объявляется с квалификаторами extern C ” :

extern "C" int __stdcall MyFunc(int x,y);

Если внешних функций несколько, их можно перечислить внутри секции extern :

extern "C"{

int __stdcall MyFunc(int x,y);

int __cdecl YourFunc(void* param);

Опция extern указывает компилятору, что тело функции следует искать в объектных файлах или библиотеках. Квалификатор "С" нужен, чтобы компилятор С++ не искажал имена функций (иначе он «от-себя» добавляет в имя @, подчеркивания и пр., и в таком виде пытается найти имя в подключенных модулях).

    Delphi :

В секции описания процедур и функций помещается заголовок ассемблерной подпрограммы, а вместо тела пишется зарезервированное слово external . Так компилятор понимает, что тело подпрограммы нужно искать во внешнем OBJ-файле. Например

Procedure MyProc(X,Y:integer); stdcall; external ;

Function MyFunc(X,Y:integer):integer; cdecl; external ;

Если предполагается, что внешняя подпрограмма имеет переменное количество аргументов, то записывается директива varargs . Эту директиву можно использоваться только совместно с конвенцией cdecl .

Если ты впервые столкнулся с микроконтроллерами, то наверняка у тебя стал выбор на чем писать.

На Си или на Ассемблере . Выбор не прост, не зря программисты однокристальщики раскололись на два непримиримых лагеря. Одни с пеной у рта доказывают, что те кто пишут на Си лохи изнеженные, а настоящий брутальный кодер должен воспринимать только ассемблер. Другие же, усераясь, доказывают, что на ассемблере разве что лампочками помигать можно, а какой либо серьезный проект делать на низком уровне невозможно.

Но, истинна, как всегда, находится посредине. Каждый уважающий себя программер должен знать ассемблер , а вот писать должен на том, что более подходит под масштаб задачи . Особенно это касается микроконтроллеров.

Assembler+
Парадоксально, но под микроконтроллеры писать на ассемблере ничуть не сложней чем на Си. В самом деле, программирование контроллера неотделимо от его железа. А когда пишешь на ассемблере, то обращаешься со всей периферией контроллера напрямую, видишь что происходит и как это происходит. Главное, что при этом ты четко понимаешь что, как и зачем ты делаешь . Очень легко отлаживать и работа программы как на ладони.
Да и сам ассемблер изучается очень быстро, за считанные дни. Достаточно постоянно иметь перед глазами систему команд и навык программирования алгоритмов. А соответствующее состояние мозга, когда начинаешь мыслить ассемблерными конструкциями наступает очень быстро.

C-
А вот с высокими языками, например с Си, ситуация уже куда хуже. Да, повторить какой нибудь пример из обучалки, вроде вывода строки на дисплей, Сях куда проще, подключил нужную библиотеку, набрал стандартную команду и вот тебе результат. Но это только так кажется. На самом деле, стоит чуть вильнуть в сторону, как начинается темный лес. Особенно когда дело начинает касаться адресации разных видов памяти,

Напомню, что у AVR, как и подавляющего числа МК, память разделена на код и данные и адреса у них в разных адресных пространствах о которых стандарт Си ничего не знает, ведь он расчитан на некий универсальный вычислитель. А тут приходится вводить всякие модификаторы, квалификаторы и без яростного курения мануалов разобраться в этих примочках бывает очень сложно. Особенно работа с памятью осложняется тем, что программа то работает в том и другом случае, но вот если неправильно указать тип памяти, то оперативка забьется константами и в дальнейшем программу скрючит в самый неподходящий момент. Вот и найди такой глюк

работе на прерываниях, использованию многопоточных процессов, использующих одни и те же ресурсы.

Вот и получается, что программа вроде компилится, но работает совершенно не так как тебе нужно, а почему непонятно. Или мусор начинает вываливать вместо данных, или перезагружается невпопад. Или работает, но иногда чудит. Пытаешься смотреть что происходит в системных регистрах и переменных, а там каша какая то. Пытаешься понять как это получилось и тупо загниваешь, т.к. не можешь найти концы.

В таких случаях обычно открывают листинг и смотрят что там происходит в коде на уровне команд процессора, но вот засада — без знания ассемблера там делать нечего! Зато если понимаешь то как работает программа на уровне команд, то найти багу вроде срыва стека или нарушения атомарного доступа проще простого.

Ну и такие гадкие вещи как overhead . Программа на Си требует значительно больше оперативной памяти, значительно больше места во flash памяти , работает куда медленней чем ассемблерная программа. И если на обычном компьютере с его гигагерцами частот, гигабайтами оперативки и дисковой памяти это не столь критично, то вот для контроллера с жалкими килобайтами флеша, у которого частота порой не превышает 16 мегагерц, а оперативки и килобайта не наберется, такой расход ресурсов более чем критичен.

Кроме того существуют такие контроллеры как ATTiny 1x у которых либо вообще нет оперативки, либо она такая мизерная, что даже стек там сделан аппаратным. Так что на Си там ничего написать в принципе нельзя.

Assembler+
Представь, что ты прораб, а компилятор это банда джамшутов. И вот надо проделать дырку в стене. Даешь ты джамшутам отвертку и говоришь — ковыряйте. Проковряют они отверткой бетонную стену? Конечно проковыряют, вопрос лишь времени и прочности отвертки. Отвертка сточится (читай памяти процессора или быстродействия не хватит)? Не беда — дадим джамшутам отвертку побольше, благо разница в цене между большой отверткой и маленькой копеечная. В самом деле, зачем прорабу, руководителю, знать такие низкоуровневые тонкости, как прочность и толщина бетонной стены, типы инструмента. Главное дать задание и проконтроллировать выполнение, а джамшуты все сделают сами.
Задача решается? Да! Эффективно это решение? Совершенно нет! А почему? А потому что прораб не знал, что бетон твердый и отверткой его проковырять сложно. А будь прораб сам когда то рабочим, пусть даже не профи, но своими руками положил плитку, посверлил дырки, то впредь таких идиотских заданий бы не давал. Конечно, нашего прораба можно и в шараге выучить, дав ему всю теорию строения стен, инструмента, материалов. Но ты представь сколько это сухой теории придется перелопатить, чтобы чутье было интуитивным, на уровне спинного мозга? Проще дать в руки инструмент и отправить сверлить стены. Практика — лучший учитель.

Также и с ассемблером. Хочешь писать эффективные программы на высокоуровневом языке — изучи хотя бы один ассемблер, попиши на нем немного. Чтобы потом, глядя на любую Сишную строку, представлять себе во что это в итоге компилируется и как обрабатывается контроллером. Очень помогает в отладке и написании, а уж про ревесирование чужих программ я вообще не говорю.

Assembler-
Но засиживаться в ассемблерщиках и стараться все сделать на нем далеко не обязательно. Ассемблерный код тяжелей переносить с контроллера на контроллер, в нем при переносе можно наделать много незначительных, но тем не менее фатальных ошибок. Отладка которых занимает много времени. Большие проекты писать на ассемблере то еще развлечение. На ассемблере трудно сделать полноценную библиотеку, подключаемую куда угодно. Ассемблер жестко привязан к конкретному семейству контроллеров.

С+
Си хорош за счет огромного числа готового кода, который можно очень легко и удобно подключать и использовать в своих нуждах. За большую читабельность алгоритмов. За возможность взять и перетащить код, например, с AVR на ARM без особых заморочек. Или с AVR на PIC. Разумеется для этого надо уметь ПРАВИЛЬНО писать на Си, выделяя все аппаратно зависимые части в HAL .

Общий расклад примерно таков, что использование высокоуровневого языка на контроллерах с обьемом памяти меньше 8 килобайт является избыточным. Тут эффективней писать все на Ассемблере. Особенно если проект подразумевает не просто мигание светодиодом.
8-16 килобайт тут уже зависит от задачи, а вот пытаться писать на ассемблере прошивку более 16 килобайт можно, но это напоминает прокладку тоннеля под Ла Маншем с помощью зубила.

В общем, знать надо и то и другое. Настоятельно тебе рекомендую начать изучать МК с ассемблера. А как только поймешь, что на асме можешь реализовать все что угодно, любой алгоритм. Когда досконально прочувствуешь работу стека, прерываний, организацию переходов и ветвлений. Когда разные трюки и хитрости, вроде игр с адресами возврата из прерываний и процедур, переходами и конечными автоматами на таблицах и всякие извраты будут вызывать лишь интерес, но никак не взрыв мозга из серии «Аааа как это работает??? Не понимаю?!!»
Вот тогда и можно изучать Си. Причем, изучать его с дебагером в руках. Не просто изучить синтаксис (там то как раз все элементарно), а понять ЧТО и КАК делает компилятор из твоего исходника. Поржать над его тупостью или наоборот поудивляться извратам искуственного интелекта. Понять как компилятор делает ветвления, как организует циклы, как идет работа с разными типами данных, как ведется оптимизация. Где ему лучше помочь, написав в ассемблерном стиле, а где не критично и можно во всю ширь использовать языковые возможности Си.

А вот начать изучение ассемблера после Си мало кому удается. Си расслабляет, становится лень и впадлу. Скомпилировалось? Работает? Ну и ладно. А то что там быдлокод, та пофигу… =)

А как же бейсик, паскаль и прочие языки? Они тоже есть на AVR?
Конечно есть, например BascomAVR или MicroPASCAL и во многих случаях там все проще и приятней. Не стоит прельщаться видимой простотой. Она же обернется тем, что потом все равно придется переходить на Си.

Дело в том, что мир микроконтроллеров далеко не ограничивается одним семейством. Постоянно появляются новые виды контроллеров, развиваются новые семейства. Ведь кроме AVR есть еще и ARM, PIC, STM8 и еще куча прекрасных контроллеров со своими плюсами.
И под каждый из этих семейств есть Си компилятор. Ведь Си это, по сути, промышленный стандарт. Он есть везде и контроллер который не имеет под него компилятора популярным у профессионалов не станет никогда.

А вот на бейсик с паскалем, обычно, всем пофигу. Если на AVR и PIC эти компиляторы и сделали, то лишь потому, что процы эти стали особо популярны у любителей и там наверняка найдется тот, кто заинтересуется и бейсиками всякими. С другим семейством контроллеров далеко не факт, что будет также радужно. Например под STM8 или Cortex M3 я видел Pascal в лучшем случае только в виде кривых студенческих поделок. Никак не тянущих на нормальный компилятор.

Такой разный Си
С Си тоже не все гладко. Тут следует избегать компиляторов придумывающих свои диалектные фишки. Например, CodeVision AVR (CVAVR) позволяет обращаться к битам порта с помощью такого кода:

PORTB |= 1<<7;

Использование диалектов не позволит тебе скакать с компилятора на компилятор. Таскать код повсюду копипастом. Привязывает к одному конкретному компилятору и далеко не факт, что он окажется хорошим и будет поддерживать все новые контроллеры семейства.

Приплюснутый
Некоторое время назад я считал, что С++ в программировании микроконтроллеров не место. Слишком большой overhead. C тех пор мое мнение несколько поменялось.
Мне показали очень красивый кусок кода на С++, который компилился вообще во что то феерическое. Компактней и быстрей я бы и на ассемблере не факт что написал. А уж про читабельность и конфигурируемость и говорить не приходится. Все из знакомых программистов, кто видел этот код, говорили что-то вроде «Черт, а я то думал, что я знаю С++».

Так что писать на С++ можно! Но сделать компактный и быстрый код на С++ чертовски виртуозная задача, надо знать этот самый С++ в совершенстве. Судя по тому, что столь качественных примеров эффективности С++ при программировании под МК мне попадалось раз-два, то видимо пороговый уровень входа в эту область весьма и весьма велик.

В общем, как говорил Джон Кармак, «хороший С++ код лучше чем хороший С код. Но плохой С++ может быть намного ужасней чем плохой С код».


Давно хотел разобраться с этой темой. И вот наконец собрался.

Дело в том, что инструкции процессора Интел и синтаксис вставок ассемблерного кода в программы на Visual C++ не будут работать в Dev-C++ .

Потому что Dev-C++ использует компилятор GCC (бесплатный компилятор языка С++). Этот компилятор имеет встроенный ассемблер, но это не MASM и не TASM с привычным . Это ассемблер AT&T, синтаксис которого очень сильно отличается от синтаксиса MASM/TASM и подобных.

Кроме того, если в Паскале или Visual C++ вы просто используете ключевые слова - операторные скобки (в Паскале это asm...end, в Visual C++ это __asm {...}), и между этими скобками пишите инструкции ассемблера как вы привыкли, то с компилятором GCC это не проканает.

Я сначала никак не мог понять, почему. Но когда немного познакомился с , то понял.

Оказывается, в компиляторе GCC, как и в Паскале и в Visual C++, есть ключевые слова asm и __asm. Вот только это вовсе не операторные скобки!!!

По сути это функции, которые вызываются с определённым набором параметров. И в эти функции в качестве параметров передаются инструкции ассемблера!

Вот уж воистину - зачем просто, если можно сложно!

В общем, использование встроенного ассемблера GCC - это целая наука. Если интересно её освоить, то можете начать вот с (это мой перевод английского оригинала).

А здесь я просто в самых общих чертах покажу, как можно использовать вставки на ассемблере в Dev-C++ (это будет также справедливо для других средств разработки, использующих компилятор GCC).

Ассемблер AT&T

Как я уже сказал, этот ассемблер сильно отличается от привычных нам . Здесь я не буду об этом говорить. Если кому интересно, то основные отличия описаны .

Вставка на ассемблере в Dev-C++

Основной формат вставки кода ассемблера показан ниже:

asm("Здесь код на ассемблере" );

/* помещает содержимое ecx в eax */ asm("movl %ecx %eax"); /* помещает байт из bh в память, на которую указывает eax */ __asm__("movb %bh (%eax)");

Как вы могли заметить, здесь используются два варианта встраивания ассемблера: asm и __asm__. Оба варианта правильные. Следует использовать __asm__, если ключевое слово asm конфликтует с каким-либо участком вашей программы (например, в вашей программе есть переменная с именем asm).

Если встраивание кода на ассемблере содержит более одной инструкции, то мы пишем по одной инструкции в строке в двойных кавычках, а также суффикс ’\n’ и ’\t’ для каждой инструкции.

Asm__ ("movl %eax, %ebx\n\t" "movl $56, %esi\n\t" "movl %ecx, $label(%edx,%ebx,$4)\n\t" "movb %ah, (%ebx)");

Однако в большинстве случаев требуется обмен данными между кодом на ассемблере и переменными, которые объявлены в исходных кодах на языке высокого уровня.

Это тоже возможно. Общий формат ассемблерной вставки для компилятора GCC такой:

Asm (assembler template: output operands /* не обязательно */ : input operands /* не обязательно */ : list of clobbered registers /* не обязательно */);

Не буду здесь подробно всё это расписывать, так как это уже сделано . Там же вы найдёте все подробности использования встроенного ассемблера компилятора GCC (ну хотя не все, а основные).

Я же здесь приведу пример, и на этом успокоюсь.

Для начала не очень хороший пример.

Int x = 0, y = 0; cout

Не очень хороший он потому, что мы изменяем значения регистров, а потом получаем значение регистра eax в переменную х. Но при этом мы не заботимся о том, что состояние этих регистров может быть изменено где-то в другом месте. Так что это может привести к потенциальным неожиданностям.

Теперь попробуем сделать всё чуть более правильно (хотя и не идеально).

Int y = 15, z = 10; cout

Здесь в ассемблерный код мы передаём значения переменных y и z. Значение у помещается в регистр еах (на это указывает буква “a”), а значение z помещается в регистр ebx (на это указывает буква “b”).

Сам ассемблерный код выполняет сложение значений регистров eax и ebx, и помещает результат в eax. А уже этот результат выводится в переменную y. То, что у - это выходная переменная, определяет модификатор “=”.

Ну вот как-то так. Это, конечно, в самых общих чертах. Если кого интересуют подробности, то см. .

Что такое Ассемблер

Ассемблер - низкоуровневый язык программирования. Для каждого процессора существует свой ассемблер. Программируя на ассемблере вы непосредственно работаете с аппаратурой компьютера. Исходный текст на языке ассемблера состоит из команд (мнемоник), которые после компиляции преобразуются в коды команд процессора.

Разработка программ на ассемблере - очень тяжёлая штука. Взамен потраченному времени вы получаете эффективную программу. Программы на ассемблере пишут, когда важен каждый такт процессора. На ассемблере вы даёте конкретные команды процессору и никакого лишнего мусора. Этим и достигается высокая скорость выполнения вашей программы.

Чтобы грамотно использовать ассемблер необходимо знать программную модель микропроцессорной системы. С точки зрения программиста микропроцессорная система состоит из:

  1. Микропроцессора
  2. Памяти
  3. Устройств ввода/вывода.

Программная модель хорошо описана в литературе .

Синтаксис Ассемблера

Общий формат строки программы на ассемблере

<Метка>: <Оператор> <Операнды> ; <Комментарий>

Поле метки. Метка может состоять из символов и знаков подчёркивания. Метки используются в операциях условного и безусловного перехода.

Поле оператора. В этом поле содержится мнемоника команды. Например мнемоника mov

Поле операндов. Операнды могут присутствовать только если присутствует Оператор (поле оператора). Операндов может не быть, а может быть несколько. Операндами могут быть данные, над которыми необходимо выполнить какие-то действия (переслать, сложить и т.д.).

Поле комментария. Комментарий нужен для словесного сопровождения программы. Всё, что стоит за символом ; считается комментарием.

Первая программа на языке Ассемблера

В этой статье будет использоваться ассемблер для i80x86 процессора и использоваться следующее программное обеспечение:

  • TASM - Borland Turbo Assembler - компилятор
  • TLINK - Borland Turbo Linker - редактор связей (компоновщик)

Если быть конкретным, то Tasm 2.0.

По традиции наша первая программа будет выводить строку "Hello world!" на экран.

Файл sample.asm

Model small ; Модель памяти.stack 100h ; Установка размера стека.data ; Начало сегмента данных программы HelloMsg DB "Hello World!",13,10,"$" .code ; Начало сегмента кода mov ax,@DATA ; Пересылаем адрес сегмента данных в регистр AX mov ds,ax ; Установка регистра DS на сегмент данных mov ah,09h ; DOS функция вывода строки на экран mov dx,offset HelloMsg ; Задаём смещение к началу строки int 21h ; Выводим строку mov ax,4C00h ; DOS функция выхода из программы int 21h ; Выход из программы end

Как вы могли заметить, что программа разделена на сегменты: сегмент данных, сегмент кода и есть ещё стековый сегмент.

Рассмотрим всё по порядку.

Директива.model small задаёт модель памяти. Модель small - это 1 сегмент для кода, 1 сегмент для данных и стека т.е. данные и стек находятся в одном сегменте. Бывают и другие модели памяти, например: tiny, medium, compact. В зависимости от выбранной вами модели памяти сегменты вашей программы могут перекрываться или могут иметь отдельные сегменты в памяти.

Директива.stack 100h задаёт размер стека. Стек необходим для сохранения некоторой информации с последующим её восстановлением. В частности стек используется при прерываниях. В этом случае содержимое регистра флагов FLAGS, регистра CS и регистра IP сохраняются в стеке. Далее идёт выполнение прерывающей программы, а потом идёт восстановление значений этих регистров.

  • Регистр флагов FLAGS содержит признаки, которые формируются после выполнения команды процессором.
  • Регистр CS (Code Segment) содержит адрес сегмента кода.
  • Регистр IP (Instruction Pointer) - указатель команд. Он содержит адрес команды, которая должная выполниться следующей (Адрес относительно сегмента кода CS).

Более подробное описание выходит за рамки простой статьи.

Директива.data определяет начало сегмента данных вашей программы. В сегменте данных определяются "переменные" т.е. идёт резервирование памяти под необходимые данные. После.data идёт строка
HelloMsg DB "Hello World!",13,10,"$"

Здесь HelloMsg - это символьное имя, которое соответствует началу строки "Hello World!" (без кавычек). То есть это адрес первого символа нашей строки относительно сегмента данных. Директива DB (Define Byte) определяет область памяти доступную по-байтно. 13,10 - коды символов Новая строка и Возврат каретки, а символ $ необходим для корректной работы DOS функции 09h. Итак, наша строка будет занимать в памяти 15 байт.

Директива.code определяет начало сегмента кода (CS - Code Segment) программы. Далее идут строки программы содержащие мнемоники команд.

Расскажу о команде mov.

mov <приёмник>, <источник>

Команда mov - команда пересылки. Она пересылает содержимое источника в приёмник. Пересылки могут быть регистр-регистр, регистр-память, память-регистр, а вот пересылки память-память нет т.е. всё проходит через регистры процессора.

Чтобы работать с данными необходимо настроить регистр сегмента данных. Настройка состоит в том, что мы записываем адрес сегмента данных @DATA в регистр DS (Data Segment). Непосредственно записать адрес в этот регистр нельзя - такова архитектура, поэтому мы используем регистр AX. В AX мы записываем адрес сегмента кода

а потом пересылаем содержимое регистра AX в регистр DS.

После этого регистр DS будет содержать адрес начала сегмента данных. По адресу DS:0000h будет содержаться символ H. Я предполагаю, что вы знаете о сегментах и смещениях.

Адрес состоит из двух составляющих <Сегмент>:<Смещение>, где Сегмент это 2 байта и смещение - 2 байта. Получается 4 байта для доступа к любой ячейке памяти.

mov ah,09h
mov dx,offset HelloMsg
int 21h

Тут мы в регистр AH записываем число 09h - номер функции 21-го прерывания, которая выводит строку на экран.

В следующей строке мы в регистр DX записываем адрес(смущение) к началу нашей строки.

Далее мы вызываем прерывание 21h - это прерывание функций DOS. Прерывание - когда выполняющаяся программа прерывается и начинает выполнятся прерывающая программа. По номеру прерывания определяется адрес подпрограммы DOS, которая выводит строку символов на экран.

У вас наверняка возникнет вопрос: А почему мы записываем номер функции 09h в регистр AH ? И почему смещение к строке записываем в регистр DX ?
Ответ простой: для каждой функции определены конкретные регистры, которые содержат входные данные для этой функции. Посмотреть какие регистры нужны конкретным функциям вы можете в help"е.

mov ax,4C00h
int 21h

mov ax,4C00h - пересылаем номер функции в регистр AX. Функция 4C00h - выход из программы.

int 21h - выполняем прерывание (собственно выходим)

end - конец программы.

После директивы end компилятор всё игнорирует, поэтому можете там писать всё, что угодно:)

Если вы дочитали до конца, то вы герой!

Майко Г.В. Ассемблер для IBM PC: - М.: "Бизнес-Информ", "Сирин" 1999 г. - 212 с.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows