Встроенный ассемблер в borland си. Язык ассемблер. Команды и основы ассемблера. Размещение данных и операторов в тексте программы

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

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

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

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 тех пор мое мнение несколько поменялось.
Мне показали очень красивый кусок кода на С++, который компилился вообще во что то феерическое. Компактней и быстрей я бы и на ассемблере не факт что написал. А уж про читабельность и конфигурируемость и говорить не приходится. Все из знакомых программистов, кто видел этот код, говорили что-то вроде «Черт, а я то думал, что я знаю С++».

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

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

Многие из нас изучали ассемблер в университете, но почти всегда это ограничивалось простыми алгоритмами под DOS. При разработке программ для Windows может возникнуть необходимость написать часть кода на ассемблер, в этой статье я хочу рассказать вам, как использовать ассемблер в ваших программах под Visual Studio 2005.

Создание проекта

В статье мы рассмотрим как вызывать ассемблер из С++ кода и обратно, передавать данные, а также использовать отладчик встроенный в Visual Studio 2005 для отладки кода на ассемблер.

Для начала нам нужно создать проект. Включаем Visual Studio, выбираем File > New > Project. В Visual Studio нет языка ассемблер в окне выбора типа проекта, поэтому создаем С++ Win32 проект. В окне настроек нового проекта выбираем «Empty Project».

По умолчанию Visual Studio не распознает файлы с кодом на ассемблер. Для того чтобы включить поддержку ассемблер нам необходимо настроить в проекте условия сборки указав какой программой необходимо компилировать файлы *.asm. Для этого выбираем пункт меню «Custom Build Rules...».

В открывшемся окне мы можем указать специальные правила компиляции для различных файлов, Visual Studio 2005 уже имеет готовое правило для файлов *.asm, нам необходимо лишь включить его, установив напротив правила «Microsoft Macro Assembler» галочку.

Добавление исходного кода

Перейдем к написанию исходного кода нашего проекта. Начнем с добавления исходного кода на c++. Добавим новый файл в папку Source Files. В качестве Template выбираем C++ File и вводим желаемое имя файла, например main.cpp. Напишем функцию, которая будет считывать имя введенное пользователем, оформив это в виде функции readName() которая будет возвращать ссылку на считанное имя. Мы получим примерно следующее содержимое файла:

#include void main () { printf("Hello, what is your name?\n"); } void* readName() { char name; scanf("%s", &name); return &name; }

Теперь, когда мы знаем имя пользователя мы можем вывести приветствие, его будет выводить функция sayHello() которую мы напишем на ассемблер, чтобы использовать эту функцию сначала мы должны указать что она будет определена в другом файле, для этого добавим блок к main.cpp:

Extern "C" { void sayHello(); }

Этот блок говорит компилятору, что функция sayHello() будет объявлена в другом файле и будет иметь правила вызова «C». Компилятор C++ искажает имена функций так, что указание правил вызова обязательно. Кроме того мы хотим использовать функцию readName() из функции sayHello(), для этого необходимо добавить extern «C» перед определением функции readName(), это позволит вызывать эту функцию из других файлов используя правила вызова «C».

Пришло время добавить код на ассемблер, для этого добавим в Source Folder новый файл. Выбираем тип Text File (.txt) и в поле название заменяем.txt на.asm, назовем наш файл hello.asm. Объявим функцию sayHello() и укажем внешние функции, которые мы хотим использовать. Получим следующий код:

686 .MODEL FLAT, C .STACK .DATA helloFormat BYTE "Hello %s!",10,13,0 .CODE readName PROTO C printf PROTO arg1:Ptr Byte, printlist: VARARG sayHello PROC invoke readName invoke printf, ADDR helloFormat, eax ret sayHello ENDP END

Теперь мы можем запустить проект, для этого просто выбираем Debug > Start Without Debugging или нажимаем комбинацию Ctrl-F5. Если все сделано верно, вы увидите окно программы:

Немного усложним задачу, попробуем написать на ассемблер функцию принимающую параметр и возвращающую значение. Для примера напишем функцию calcSumm() которая будет принимать целое число и возвращать сумму его цифр. Изменим наш код на С++ добавив в него информацию о функции calcSumm, ввод числа и собственно вызов функции. Добавим функцию в файл hello.asm, возвращаемое значение помещается в eax, параметры объявляются после ключевого слова PROC. Все параметры можно использовать в коде процедуры, они автоматически извлекутся из стека. Также в процедурах можно использовать локальные переменные. Вы не можете использовать эти переменные вне процедуры. Они сохранены в стеке и удаляются при возврате из процедуры:

686 .MODEL FLAT, C .STACK .DATA helloFormat BYTE "Hello %s!",10,13,0 .CODE readName PROTO C printf PROTO arg1:Ptr Byte, printlist: VARARG sayHello PROC invoke readName invoke printf, ADDR helloFormat, eax ret sayHello ENDP calcSumm PROC a:DWORD xor esi, esi mov eax, a mov bx, 10 @div: xor edx, edx div bx add esi, edx cmp ax, 0 jne @div mov eax, esi ret calcSumm ENDP END

Запустив проект мы увидим следующий результат выполнения:

Отладка

Конечно в данной задаче нет ничего сложного и она вовсе не требует использования ассемблер. Более интересным будет рассмотреть, а что же нам дает Visual Studio для разработки на ассемблер. Попробуем включить режим отладки и установим точку остановки в hello.asm, запустим проект, мы увидим следующее:

Окно Disassembly (Debug > Windows > Disassembly) показываем команды ассемблер для данного объектного файла. Код который мы написали на С++ показывается черным цветом. Disassembled code показывается серым после соответствующего ему кода на C++/ассемблер. Окно Disassembly позволяет отлаживать код и осуществлять stepping по нему.

Окно регистров (Debug > Windows > Registers) позволяет посмотреть значение регистров.

Окно памяти (Debug > Windows > Memory) позволяет посмотреть дамп памяти, слева мы видим шестнадцатеричные адрес, справа шеснадцатеричные значения соответствующих ячеек памяти, можно перемещаться, вводя адрес в соответствующее поле в верху окна.

В статье будут рассмотрены основы языка ассемблер применительно к архитектуре win32. Он представляет собой символическую запись машинных кодов. В любой электронно-вычислительной машине самым низким уровнем является аппаратный. Здесь управление процессами происходит командами или инструкциями на машинном языке. Именно в этой области ассемблеру предназначено работать.

Программирование на ассемблер

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

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

Регистры

Регистрами в языке ассемблер называют ячейки памяти, расположенные непосредственно на кристалле с АЛУ (процессор). Особенностью этого типа памяти является скорость обращения к ней, которая значительно быстрее оперативной памяти ЭВМ. Она также называется сверхбыстрой оперативной памятью (СОЗУ или SRAM).

Существуют следующие виды регистров:

  1. Регистры общего назначения (РОН).
  2. Флаги.
  3. Указатель команд.
  4. Регистры сегментов.

Есть 8 регистров общего назначения, каждый размером в 32 бита.

Доступ к регистрам EAX, ECX, EDX, EBX может осуществляться в 32-битовом режиме, 16-битовом - AX, BX, CX, DX, а также 8-битовом - AH и AL, BH и BL и т. д.

Буква "E" в названиях регистров означает Extended (расширенный). Сами имена же связаны с их названиями на английском:

  • Accumulator register (AX) - для арифметических операций.
  • Counter register (CX) - для сдвигов и циклов.
  • Data register (DX) - для арифметических операций и операций ввода/вывода.
  • Base register (BX) - для указателя на данные.
  • Stack Pointer register (SP) - для указателя вершины стека.
  • Stack Base Pointer register (BP) - для индикатора основания стека.
  • Source Index register (SI) - для указателя отправителя (источника).
  • Destination Index register (DI) - для получателя.

Специализация РОН языка ассемблер является условной. Их можно использовать в любых операциях. Однако некоторые команды способны применять только определенные регистры. Например, команды цикла используют ESX для хранения значения счетчика.

Регистр флагов. Под этим подразумевается байт, который может принимать значения 0 и 1. Совокупность всех флагов (их порядка 30) показывают состояние процессора. Примеры флагов: Carry Flag (CF) - Флаг переноса, Overflow Flag (OF) - переполнения, Nested Flag (NT) - флаг вложенности задач и многие другие. Флаги делятся на 3 группы: состояние, управление и системные.


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

Регистры сегментов (CS, DS, SS, ES, FS, GS). Их наличие в ассемблере продиктовано особым управлением оперативной памятью, чтобы увеличить ее использование в программах. Благодаря им можно было управлять памятью размером до 4 Гб. В архитектуре Win32 необходимость в сегментах отпала, но названия регистров сохранились и используются по-другому.

Стек

Это область памяти, выделенная для работы процедур. Особенность стека заключается в том, что последние данные, записанные в него, доступны для чтения первыми. Или иными словами: первые записи стека извлекаются последними. Представить этот процесс себе можно в качестве башни из шашек. Чтобы достать шашку (нижнюю шашку в основание башни или любую в середине) нужно сначала снять все, которые лежат сверху. И, соответственно, последняя положенная на башню шашка, при разборе башни снимается первой. Такой принцип организации памяти и работы с ней продиктован ее экономией. Стек постоянно очищается и в каждый момент времени одна процедура использует его.


Идентификаторы, целые числа, символы, комментарии, эквивалентность

Идентификатор в языке программирования ассемблер имеет такой же смысл, как и в любом другом. Допускается использование латинских букв, цифр и символов "_", ".", "?", "@", "$". При этом прописные и строчные буквы эквивалентны, а точка может быть только первым символом идентификатора.

Целые числа в ассемблере можно указывать в системах отсчета с основаниями 2, 8, 10 и 16. Любая другая запись чисел будет рассматриваться компилятором ассемблера в качестве идентификатора.

В записи символьных данных допускается использовать как апострофы, так и кавычки. Если в символьной строке требуется указать один из них, то правила следующие:

  • в строке, заключенной в апострофы, кавычки указываются один раз, апостроф - дважды: "can""t", " he said "to be or not to be" ";
  • для строки, заключенной в кавычки, правило обратное: дублируются кавычки, апострофы указываются как есть: "couldn"t", " My favourite bar is ""Black Cat"" ".

Для указания комментирования в языке ассемблер используется символ точка с запятой - ";". Допустимо использовать комментарии как в начале строк, так и после команды. Заканчивается комментарий переводом строки.

Директива эквивалентности используется схожим образом тому, как в других языках указывают константные выражения. Эквивалентность указывается следующим способом:

Таким образом в программе все вхождения будут заменяться на, на месте которого допустимо указывать целое число, адрес, строку или другое имя. Директива EQU похожа по своей работе на #define в языке С++.

Директивы данных

Языки высокого уровня (C++, Pascal) являются типизированными. То есть, в них используются данные, имеющие определенный тип, имеются функции их обработки и т. д. В языке программирования ассемблер подобного нет. Существует всего 5 директив для определения данных:

  1. DB - Byte: выделить 1 байт под переменную.
  2. DW - Word: выделить 2 байта.
  3. DD - Double word: выделить 4 байта.
  4. DQ - Quad word: выделить 8 байтов.
  5. DT - Ten bytes: выделить 10 байтов под переменную.

Буква D означает Define.

Любая директива может быть использована для объявления любых данных и массивов. Однако для строк рекомендуется использовать DB.

Синтаксис:

В качестве операнда допустимо использовать числа, символы и знак вопрос - "?", обозначающий переменную без инициализации. Рассмотрим примеры:

Real1 DD 12.34 char db "c" ar2 db "123456",0 ; массив из 7 байт num1 db 11001001b ; двоичное число num2 dw 7777o ; восьмеричное число num3 dd -890d ; десятичное число num4 dd 0beah ; шестнадцатеричное число var1 dd ? ; переменная без начального значения ar3 dd 50 dup (0) ; массив из 50 инициализированных эл-тов ar4 dq 5 dup (0, 1, 1.25) ; массив из 15 эл-тов, инициализированный повторами 0, 1 и 1.25


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

Дело в том, что инструкции процессора Интел и синтаксис вставок ассемблерного кода в программы на 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: Регистры и параметры


1.1 Использование регистров


В создаваемых ассемблерных программах можно использовать все регистры процессора. Но чтобы предотвратить путаницу с функциями С и С++, необходимо восстанавливать bp, cs, sp и ss, которые они имели до запуска созданной подпрограммы. Тогда можно быть совершенно уверенным, что обращение к другим функциям не изменит эти регистры. Также нельзя забывать, что С использует регистры si и di для регистровых переменных, поэтому при использовании встроенного ассемблера замедляется общая работа программы.

К регистрам ax, bx, cx, dx и es можно обращаться свободно и не нужно резервировать их значения до окончания подпрограммы. Эта свобода касается и других функций, поэтому надо помнить, что эти регистры изменяться, если вызываются функции С и С++ из ассемблерных подпрограмм.


1.2 Ассемблерные операторы Inline


Ассемблерные операторы inline начинаются словом asm, за которым следует инструкция и ее операнды. Например, чтобы синхронизировать программу с внешним сигналом прерывания, можно написать:

/* ожидание прерывания*/

printf(“Прерывание получено\n”)

Когда ранние версии Turbo C компилируют программу со встроенными командами asm, компилятор сперва создает ассемблерный текст для всей программы, вставляя в текст наши ассемблерные инструкции вместе с откомпилированным кодом для остальных операторов С. Затем компилятор вызывает Turbo Assembler и Linker (компоновщик), чтобы провести ассемблирование и подключить программу к конечному файлу кода. Более поздние версии Turbo и Borland C++ могут компилировать операторы asm без вызова TASM. Полный синтаксис asm:

asm[метка] мнемоника/директива операнды [;]

Точки с запятыми в конце строк asm и комментарии С, расположенные между /*и*/ удаляются из текста перед ассемблированием, поэтому их можно опускать в тексте программы.


1.3 Размещение данных и операторов в тексте программы


Каждая строка текста программы С и С++ находится либо внутри, либо снаружи функции, и операторы asm могут вставляться как в одном, так и в другом месте. Конкретное положение оператора asm влияет на то, куда ассемблируется код или директива. Если оператор asm появляется снаружи функции, то он ассемблируется в сегмент данных программы, если внутри функции - в кодовый сегмент. Обычно, чтобы создать переменные, операторы asm вставляются снаружи функций; для создания кода их следует вставлять внутрь функций. Например:

asm shl , 1/*умножение count на 4*/

asm shl , 1

Переменная count объявляется в сегменте данных программы (относительно ds). Операторы внутри функции main умножают count на 4, используя вместо mul быстрые инструкции сдвига shl. Если объявлять переменные внутри функции, данные ассемблируются в кодовый сегмент, требуя особого обхождения:

asm jmp OverThere

asm shl , 1 /* умножение count на 4*/

asm shl , 1

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




Раздел 2: Особенности данных


2.1 Разделение данных


Inline операторы asm имеют свободный доступ к переменным и структурам С и C++ - одно из самых привлекательных преимуществ метода inline по сравнению с подходом посредством внешних модулей. Самое интересное в типах данных ассемблера и С++, что dq может использоваться для создания инициализированных переменных двойной длины с плавающей точкой в языке ассемблера, но в Turbo Assembler отсутствуют директивы для непосредственного создания переменных с плавающей точкой.

В операторах asm можно ссылаться на переменные типов С++. Например:

unsigned char initial

asm mov ah, 2 /* Пересылка символа в ДОС */

asm int 21h /* Стандартная выходная функция */

Беззнаковая символьная переменная initial загружается оператором asm в dl. Так как и dl, и беззнаковые символьные данные имеют длину один байт, нет необходимости в ссылке использовать определитель Byte, хотя его применение и не будет ошибкой:

asm mov dl,


2.2 Объявление ассемблерных данных


Можно объявить переменные для использования только ассемблерными операторами. Например, чтобы создать 16-битовое слово с именем TwoBytes и загрузить значение переменной в сх, можно написать:

asm TwoBytes db 1, 2

asm mov cx,

Переменная TwoBytes объявляется в сегменте данных программы (снаружи функции), с использованием директивы db, чтобы хранить в памяти 2 байта (1 и 2). Оператор ассемблера затем загружает TwoBytes в сх. Определитель Wordptr необходим для ссылки на TwoBytes как на 16-битовое слово.

Поскольку TwoBytes объявляется в операторе asm, на эту переменную нельзя ссылаться в тексте программы С или C++. Поэтому, если только не требуются отдельные переменные для ассемблерных инструкций, следует объявлять их обычным образом и ссылаться на них из ассемблерного модуля.


2.3 Разделение кода


Ассемблерные операторы inline могут вызывать функции С и C++, а операторы С и C++ обращаться к функциям, написанным полностью на ассемблере. Вся эта взаимосвязь будет хорошо рассмотрена в данном курсовом проекте.



Раздел 3: Вызов ассемблерных функций из С


3.1 Символы подчеркивания


Как показывает практика, все символы PUBLIC и EXTERN должны начинаться символами подчеркивания. Это необходимо делать только в ассемблерном модуле (но не в исходной про грамме С или C++), поскольку компилятор добавляет подчеркивание ко всем глобальным символам, если только не используется опция -u для компиляции программ. (Не стоит применять эту опцию, если только не надо также перекомпилировать всю используемую при выполнении С библиотеку, в которой предполагается, что все глобальные символы начинаются символом подчеркивания.) Если во время компоновки выходит сообщение об ошибке "undefined symbol" (неопределенный символ), то причиной может оказаться отсутствие подчеркивания в ассемблерном модуле.


3.2 Использование дальних данных


Если объявляются переменные в дальнем сегменте данных после ключевого слова FARDATA, то необходимо подготовить сегментный регистр к размещению переменных в памяти. Прежде всего, вслед за директивой FARDATA необходимо объявить данные:

OuterLimits dw ?

Затем, в кодовом сегменте, следует перед использованием переменной подготовить сегментный регистр. Одним из возможных подходов является использование оператора SEG для загрузки адреса дальнего сегмента данных:

mov ax, SEG_OuterLimits;Адресует дальний сегмент;данных

mov es, ах;посредством es

mov , dx ;Резервируется dx для пере;менной

Можно также использовать заранее определенный символ @fardata:

mov ах, @fardata

mov , dx


3.3 Вызов ассемблерных функций из С


Чаще всего asm код оформляют в виде отдельных asm функций, которые потом соединяют на этапе загрузки. Ассемблерный код должен поддерживать стиль языка С для вызова функций, которые включают передачу параметров, возврат значений и правила сохранения регистров, которые требуются для С++ функций.

Компилятор и загрузчик должны работать совместно для обеспечения вызовов между модулями. Процесс называется исключением или ломкой имен. Предусматривает наличие информации о типе аргумента. Ломка имени изменяет имя функции, показывая, что за аргумент принимает функция. Когда мы конструируем на языке С++, то ломка имен происходит автоматически.

Когда же пишется модуль на asm, который должен быть подсоединен к модулю на языке С++, необходимо позаботиться о том, чтобы asm модуль содержал ломку имен (знак подчеркивания). Это можно сделать путем написания фиктивной функции на С++ и компилировать его в asm код. И ассемблерный файл, который будет сгенерирован компилятором С++, будет содержать ломку имен, которые мы можем потом использовать при написании asm функции:

@ имя класса @ исходное имя функции $g описание типов

@ - связующий символ

$ - конец исходного имени функции

g - начало описания типов параметров

void test(int...) {}

void test(int, int) {}

void test(float, double) {}

Что будет в ассемблере?

1. @ test $ gv proc near

@ test $ gv endp


2. @ test $ gi proc near

@ test $ gi endp


3. @ test $ gii proc near

@ test $ gii endp


4. @ test $ gfd proc near

@ test $ gfd endp


BC++ разрешает использование неискаженных имен asm функций, определяя стандартные имена С функций в С программах. Для этого в программе определяется внешний С блок:

int add(int *a, int b);

________________

Add proc ...и т.д.


Определение или декларирование asm функции во внешнем С блоке избавляет программиста от необходимости определения действительного имени ассемблерной функции и повышает наглядность.

С++ передает параметры функции через стек. Перед тем как вызвать функцию С++ помещает параметры в стек, начиная с последнего.


3.4 Ассемблирование и компоновка внешних модулей


Существует несколько методов ассемблирования, компиляции и компоновки отдельных модулей (и аналогичных многофайловых программ) для создания конечной.ЕХЕ программы. Проще всего предоставить проделать все это Turbo С:

tcc cfillstr cfill.asm/*первый модуль - cfillstr.c*/

Если используется Borland C++, надо ввести следующую команду (заменить bсс на tсс для Turbo C++):

bcc cfillstr.с cfill.asm

В любом случае команда сперва компилирует CFILLSTR.C в CFILLSTR.OBJ. Затем, распознав расширение имени файла.ASM как имя ассемблерного модуля, компилятор вызывает Turbo Assembler, чтобы ассемблировать CFILL.ASM в CFILL.OBJ. И наконец, компилятор вызывает Turbo Linker для объединения модулей с объектными кодами в CFILLSTR.EXE. Если компиляции и ассемблированию подлежит небольшое количество модулей, этот одношаговый метод наиболее прост для использования.

Если у имеется большое количество модулей, можно сэкономить время, осуществляя ассемблирование и компоновку раздельно. Первым шагом следует ассемблировать все.ASM файлы. Поскольку пример flllsrting обладает только одним таким файлом, все можно проделать с помощью единственной команды

tasm /ml cfill.asm

Опция /ml включает различение строчных и прописных символов, чтобы к примеру слова UpAndDown и upanddown рассматривались как различные - как это принято в программах С и C++. (Turbo Assembler обычно не обращает внимания на то, в каком регистре набраны символы, поэтому опция /ml необходима во избежание ошибок при компоновке.) После ассемблирования всех внешних модулей откомпилировать основную программу. Опять же, поскольку в этом примере имеется только один файл.С, для этого необходима только одна команда

tcc -с cfillstr.c

Если используется Borland C++, надо применить следующую команду (заменить bсс на tсс для Turbo C++):

bсс -с cfillstr.c

Опция -с означает "только компилировать", вызывая создание CFILLSTR.OBJ, но не компоновку программы в законченный кодовый файл. Для включения всех модулей необходимо выполнить этот шаг самим, вызывая Turbo Linker для объединения файлов с объектным кодом с соответствующими библиотечными подпрограммами для создания CFILLSTR.EXE. Существует два метода компоновки. Сначала рассмотрим более сложный метод:

t1ink с:\tc\lib\c0s cfillstr cfill, cfillstr, с:\tc\lib\cs

При использовании Borland C++4 можно применить следующую команду:

tlink с:\bc4\lib\c0s cfillstr cfill, cfillstr, с:\bc4\lib\cs

Первый член после tlink специфицирует файл объектного кода в директории \LIB для соответствующей модели памяти, в данном случае - COS.OBJ. Второй и третий члены перечисляют подлежащие компоновке файлы с объектным кодом.OBJ - они могут быть перечислены в любом порядке. Запятая отделяет список файлов.OBJ от имени, которое должно использоваться для конечного файла, в данном случае - CFILLSTR-EXE. Две запятые, следующие за этим, показывают место необязательного файла карты (*.map), не создающегося в данном примере. И наконец, специфицируется рабочая библиотека - также в каталоге \LIB.

Имена файла с объектным кодом COS и библиотечным файлом CS должны соответствовать модели памяти, используемой программой.

Упрощенный (но не очень быстрый) метод компоновки отдельных модулей состоит в использовании компилятора в качестве "первой части" Turbo Linker. Другими словами, вставляя различные команды компиляции, можно пропустить компиляцию и перейти прямо к компоновке. Это дает возможность избежать необходимости специфицировать имена рабочих библиотечных файлов и, следовательно, упрощает команду компоновки. Например, для ассемблирования, компиляции и компоновки CFILLSTR этим методом требуются три команды:

tasm /ml cfill.asm

tee -с cfillstr.c

tee -ms cfillstr.obj cfill.obj

Для Borland C++ надо ввести следующие команды (заменить bсс на tсс для Turbo C++):

tasm /ml cfill.asm

bee -с cfillstr.с

bee -ms cfillstr.obj cfill.obj

Первые две команды - те же, что были описаны выше. Третья вызывает компилятор во второй раз, используя опцию -ms для определения модели памяти, в данном случае small (малая). Вслед за опцией модели памяти идут файлы с объектным кодом, подлежащие компоновке. Хотя приходится включать расширение имен файлов.OBJ в перечисление каждого файла, этот не очень длинный метод компоновки упрощает большую часть черновой работы по непосредственному запуску Turbo Linker. Для того, чтобы узнать, какую опцию надо писать при TCC, можно воспользоваться следующей таблицей.


Таблица 3.1: Имена файлов рабочей библиотеки





Раздел 4: Вызов функций С из языка ассемблера


4.1 Вызов С и С++ из ассемблера


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

Если функция не обладает параметрами, процесс достаточно прост. Надо объявить функцию С или C++ в директиве EXTRN и воспользоваться инструкцией call:

EXTRN _cfuntion:proc

Здесь предполагается, что функция, названная _cfunction, существует в программе, подлежащей компоновке вместе с ассемблерным модулем.

Если функции требуются параметры, процесс усложняется. Простые параметры, такие как символы и целые числа часто передаются непосредственно в стек. Сложные переменные, такие как строки, структуры и множества, передаются посредством ссылок, т.е. по адресу. Кроме того, многие функции возвращают результат в специфичные регистры. При вызове функций С или C++ из языка ассемблера надо самим позаботиться о подобных нюансах.

Сперва рассмотрим простейший случай вызова функции с одним целочисленным параметром:

void showscore(int thescore)

printf(“\nThe score is: %d\n, thescore);

Чтобы вызвать функцию showscore из ассемблерного модуля, передавая значение переменной типа слова в качестве thescore, можно написать:

EXTRN showscore: proc

mov ах, 76 ; Присвоение score регистру

push ах; Передача параметра в стек

call _showscore ; Вызов функции С

pop ах; Фиксация стека


Прежде всего, значение score присваивается ах (любой другой регистр точно так же подойдет для этого), а затем выталкивается в стек перед вызовом showscore. После возврата из функции слово выталкивается из стека. Это необходимо потому, что в С и C++ вызывающая программа удаляет параметры из стека. Если имеется несколько параметров, может быть, будет лучше просто прибавить их общее число байтов к sp. Например, чтобы вызвать функцию, которая оперирует четырьмя 16-битовыми параметрами, можно воспользоваться следующими командами:

push ; Выталкивание четырех переменных

push ; (не показанных) в стек

call _aCfunction ; Вызов функции С

add sp, 8 ; Удаление параметров

Выталкивание нескольких параметров осуществляется в порядке, обратном тому, в каком они объявляются функции С или C++. Исходя из предположения, что функция fillstring определена как

void fillstring(unsigned char far *thestring, int stringLength, char fillchar);

для вызова этой функции из языка ассемблера и заполнения строковой переменной пробелами требуются несколько шагов. Сперва ассемблерный модуль объявляется строковой переменной:

Astring db 80 dup (0)

Затем этот же модуль объявляет fillstring в директиве EXTRN и вызывает функцию для заполнения строчной переменной пробелами:

EXTRN _fillstring: ргос

xor ah, ah ; Обнуление ст. половины ах

mov al,’ ‘ ; Присвоение пробела а1

push ах; Проталкивание пар-ра fillchar

mov ах, 79 ; Присвоение длины строки ах

push ах;Проталкивание пар-ра дл. строки

push ds ;Проталкивание сег-та адреса строки

mov ах, offset _astring ;Присвоение смещения адреса ах

push ах;Проталкивание смещ. адреса строки

call _fillstring ; Вызов функции

add sp, 8 ; Удаление параметров из стека

Каждый из параметров - заполняющий символ, длина строки и 32-битовый указатель строковой переменной- проталкивается в стек в порядке, обратном перечисленному в определении функции. Применительно к указателю - сегмент адреса проталкивается перед смещением. После обращения к _fillstring к указателю стека sp добавляются 8 байт, удаляя параметры из стека.

Несмотря на то что в этом примере функция _fillstring в действительности написана на языке ассемблера, вызовы функций С и C++ ничем не отличаются.


4.2 Локальные переменные


В дополнение к переменным, объявленным в сегменте данных либо общим с программой С и С++, можно использовать локальные переменные, помещенные в стек создаваемых ассемблерных модулей. Локальные переменные существуют только во время выполнения функции. Стековая часть создается для переменных при запуске функции, а затем очищается перед ее завершением. Таким образом, другие функции могут использовать эти же области памяти для своих собственных локальных переменных, снижая общий объем памяти, требуемой для всей программы. Например:

printer("%d", i);

Целая переменная i помещается в памяти в стек при запуске функции countup и существует только до тех пор пока выполняется эта функция. В ассемблерном модуле можно проделать тоже самое с помощью директивы LOCAL. Вот пример законченной функции:

PROС _cfunction NEAR

LOCAL i:Word=stacksize

sub sp, stacksize

;--Код, использующий локальную переменную [i]

ret ; Возврат в точку вызова

Директива LOCAL в этом примере подготавливает переменную i типа Word (слово). Указание = stacksize назначает общее число байтов, занимаемое всеми локальными переменными - в данном случае 2 байта. Это значение вычитается из sp после подготовки адресации переменных в стек. Затем, для ссылки на i, используются такие инструкции, как mov, inc и crop. Благодаря директиве LOCAL ссылки типа [i] переводятся следующим образом:

и т.д. При использовании LOCAL нет необходимости вычислять отрицательные смещения относительно bp, чтобы определить местоположение переменных в стеке, -достаточно воспользоваться именами переменных.

Поскольку bp не изменяется во время выполнения этой функции, можно восстановить sp по средством bp, удаляя область локальной переменной из стека, или прибавить stacksize к sp с помощью команды

add sp, stacksize

Подходят оба метода, но восстановление sp посредством bp - быстрее. Можно также объявить несколько локальных переменных операторами, подобными следующему:

LOCAL i:Word; j:Word; c:Byte=stacksize

Теперь, после вычитания stacksize из указателя стека для резервирования области в стеке, можно использовать три локальные переменные - i, j и с. (Необходимо всегда делать LOCAL, что упрощает адресацию локальных переменных, это не создает область для переменных в памяти.)


4.3 Передача аргументов


Совместное использование C++ и ассемблера становится более сложным, когда к функциям добавляются аргументы. Приходится очень внимательно программировать, обращаясь к функциям из различных модулей и выбирая аргументы из стека. Но следующая директива берет на себя задачу сама произвести ломку имен и занести их в стек:

ARG c_offset:byte, k_pffset:word

Аргументы, перечисленные таким образом, не являются объектами данных; они смещаются в стеке относительно регистра bр. Использование ARG подобным образом позволяет ассемблеру вычислить смещения вместо нас - но мы должны специфицировать правильные типы данных. Символьная переменная в C++ является байтом в ассемблере, целая в C++ - эквивалентом ассемблерного слова и т.д.

Обратный процесс - передача аргументов из языка ассемблера в C++ - требует иной тактики:

proc _asmfunction C c_arg:byte, k_arg:word

“C” после имени функции указывает, что аргументы приводятся для языка С (т.е. они выталкиваются в стек в порядке справа налево). Остальное также, как и для директивы ARG.

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




Раздел 5: Курсовая программа


5.1 Постановка задачи


В курсовой требовалось написать программу, вычисляющую значение многочлена n-ой степени по вектору размерности n коэффициентов многочлена и по некой переменной х, задаваемые пользователем. Необходимо было реализовать многомодульную связь (два модуля: на С и на ассемблере), а также выразить наглядно связь ассемблера с языком С.


5.2 Блоксхема программы


Программа реализовывалась по следующей блоксхеме:


Очевидно, что была реализована связь в обе стороны.

5.3 Модуль на С++


/*Это часть на С++, есть еще на ассемблере*/


extern "C" void vvod();/*ассемблерная процедура*/

extern "C" void mnogochlen(int *vec);/*функция в С*/

extern int *vect;/*вектор коэффициентов, объявленный*/

/*внешней переменной*/

vvod();/*Вызов ассемблерной процедуры*/


extern "C"/*Начало функции на С*/

void mnogochlen(int *vect)

for(i=m;i>=0;i--)/*Вычисление ответа*/

summa=summa+vect[i]*fact;


5.4 Модуль на ассемблере


so db "введите вектор коэффициентов","$"

extrn _m:word, _vec:word; Внешние аргументы

extrn _mnogochlen:proc; функция из С++

public _vvod; местная процедура

push di;Сохранение значений этих регистров

push si;необходимо, т.к. они используются С++

MOV ah,09h ;начало ввода вектора перестановок

MOV dx,offset so

exit: MOV [_vec],di

ADD si,2;Данные типа слово, прибавляем 2

pop di;извлечение сохраненных регистров

pop si;перед возвратом в С++

MOV ax,offset [_vec]

call _mnogochlen; Вызов функции С++

ADD sp,4;Удаление элементов из стека

POP bp;адрес возврата


5.5 Анализ проведенной работы


При работе в многомодульном режиме главные трудности возникают в использовании регистров. Из-за несохранения системных регистров, используемых С++, программа может просто “повиснуть”. Также следовало сделат выбор - какой модуль сделать главным. Но практика показывает, что следует производить вызов языков низкого уровня из высокого, а не наоборот. В общем, все трудности были преодолены и программа появилась в свет.



Введение


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

В чем же дело? А в том, что компиляторы обобщают решаемые ими задачи. В C++ существует только один способ написания цикла FOR. В то же время с помощью ассемблера этот цикл можно организовать с помощью ассемблера этот цикл можно организовать десятками способов. Чтобы компилятор выбирал идеально подходящие в каждой конкретной ситуации методы реализации алгоритма и при этом учитывая их влияние на остальные части программы - он должен обладать интеллектом гения, вкусом художника и интуицией предсказателя. Сегодняшние компиляторы с языков высокого уровня достаточно сообразительны, но не до такой степени.

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

Во многих случаях единственный способ для прибавления программе скорости - немного дополнить совершенный “С”, используя один из двух методов:

    Использование встроенных операторов (операторов Inline)

    Использование внешних функций

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



Список использованной литературы


1. Том Сван “Освоение Turbo Assembler”, Диалектика, Киев, 1996 г.


2. Березин Б.И., Березин С.Б. “Начальный курс С и С++”, Диалог МИФИ, Москва, 1996 г.


3. Лекции Комлевой Нины Викторовны по предмету “Языки программирования и методы трансляции”




Введение 3 Раздел 1: Регистры и параметры 5 1.1 Использование регистров 5 1.2 Ассемблерные операторы Inline 5 1.3 Размещение данных и операторов в программе 6 Раздел 2: Особенности данных 7 2.1 Разделение данных 7 2.2 Объявление ассемблерных данных 7 2.3 Разделение кода 8 Раздел 3: Вызов ассемблерных функций из С 9 3.1 Символы подчеркивания 9 3.2 Использование дальних данных 9 3.3 Вызов ассемблерных функций из С 10 3.4 Ассемблирование и компоновка внешних модулей 11 Раздел 4: Вызов функций С из ассемблера 15 4.1 Вызов С и С++ из ассемблера 15 4.2 Локальные переменные 17 4.3 Передача аргументов 18 Раздел 5: Курсовая программа 20 5.1 Постановка задачи 20 5.2 Блоксхема программы 20 5.3 Модуль на С++ 20 5.4 Модуль на ассемблере 21 5.5 Анализ проведенной работы 23 Список использованной литературы 24

МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ЭКОНОМИКИ, СТАТИСТИКИ И ИНФОРМАТИКИ


КАФЕДРА МО ЭВМ


КУРСОВАЯ РАБОТА


на тему: “Взаимосвязь языков С и ассемблера”


Студент: Аплемах А.В.

Группа: ДП-202


Руководитель проекта:

Комлева Н.В.


Защищена “___” Мая 1997 г.




Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: