1.12.4 Использование языка ассемблера в программах на Си
Для оптимизации программ часто используют язык ассемблера (далее просто ассемблер). Поскольку этот язык практически в чистом являет собой язык микропроцессора, то получаемый с помощью него код весьма компактен и выполняется гораздо быстрее кода, получаемого из фрагмента на языке высокого уровня.
При работе с языком Си можно использовать как встроенный ассемблер, так и язык ассемблера во внешних модулях.
Рассмотрим в начале встроенный ассемблер. Достоинством его является возможность писать ассемблерные фрагменты прямо среди фрагментов на языке Си. Основным недостатком является то, что часто встроенный ассемблер обладает меньшими возможностями реального ассемблера (отсутсвие некоторых команд, директив).
Основой встроенного ассемблера является ключевое слово asm, после которого может идти или команда на языке ассемблера или блок команд, заключенных в фигурные скобки. Ниже приводится простой пример.
#include <stdio.h>
void main()
{
char * s="Печать из ассемблерного блока";
/*далее идут команды на языке ассемблера*/
asm lds bx,s
asm mov ah,2
l1:
asm mov dl,[bx]
asm cmp dl,0
asm jz l2
asm int 21h
asm inc bx
asm jmp short l1
l2:
}
Мы намеренно взяли программу из предыдущего параграфа и переписали ее на языке ассемблера. Прокоментируем ее не вдаваясь в особенности выполнения ассемблерных команд. Для вывода символа на экран его помещают в регистр dl и вызывается функция 2 21-его прерывания. На очередной символ строки указывает регистр bx. Процесс вывода символов заканчивается когда в регистр dl попадает код 0 (конец строки).
Перейдем теперь к случаю, когда к программе на языке Си подключается модуль, написанный на языке ассемблера. Подключение осуществляется на втором этапе трансляции (см. параграф 1.12.1). Ниже приведены модуль на языке ассемблера и модуль на языке Си. Причем первый содержит процедуру, вызываемую из второго.
;модуль на языке ассемблера
CODE SEGMENT
ASSUME CS:CODE
PUBLIC _PRI_STR ;процедура будет вызываться из другого модуля
_PRI_STR PROC FAR
PUSH BP
MOV BP,SP
;получаем адрес начала строки
LDS BX,[BP+6]
;номер вызываемой функции
MOV AH,2
CONT:
;очередной символ поместить в регистр dl
MOV DL,DS:[BX]
;проверяем - не конец ли строки
CMP DL,0
JZ _en
;вызов 21-его прерывания
INT 21H
;переходим к следующему символу
inc bx
;на начало цикла
JMP SHORT CONT
_en:
POP BP
;возвращаемся в вызывающий модуль
RET
_PRI_STR ENDP
CODE ENDS
END
/*Модуль на языке Си*/
#include <stdio.h>
extern void far PRI_STR(char *);
void main()
{
char * st="Печать из ассемблерного модуля.";
PRI_STR(st);
}
Коментарий.
Прежде всего, отметим, что модули должны быть согласованы по модели памяти (см. 1.12.2). Мы предполагаем, что модуль на языке Си компилируется в модели Large. В модуле на языке ассемблера согласование по модели заключается в том, что вызываемая из другого модуля процедура имеет тип Far. Оба модуля можно просто включить в проект (модуль на языке Си должен быть первым, а модуль на языке ассемблера должен иметь расширение asm) при этом для ассемблерного модуля при трансляции автоматически будет вызываться транслятор tasm.exe. Ассемблерный модуль может быть отранслирован и отдельно, тогда в проекте он должен иметь расширение obj.
Второй тип согласования - согласование имен. Мы должны учесть:
1. Трансляторы Си различают заглавные и прописные буквы, поэтому вызываемая процедура должна быть написана одинаково в обоих модулях.
2. При трансляции к именам Си впереди добавляется символ подчеркивания, что следует учесть в ассемблерном модуле.
Наша программа выполняет те же действия, что и предыдущая программа этого параграфа, т.е. печатает строку. Печать осуществляется процедурой PRI_STR, которой передается как параметр указатель на эту строку. Обращаем ваше внимание на то, что вызываемая процедура в ассмблерном модуле объявлена как PUBLIC, т.е. ее имя будет помещено в объектный модуль. В свою очередь в модуле на языке Си эта процедура объявлена как extern.
На этом мы заканчиваем рассмотрение аспектов связанных с зыком ассемблера. Подробны об языке ассемблера и его использовании в языках высокого уровня можно найти в книге [], написанной одним из авторов этих.
Глава 2. Примеры использования языка Си
2.1 Сортировка
Практически каждый алгоритм сортировки можно разбить на три части:
- сравнение, определяющее упорядоченность пары элементов;
- перестановку, меняющую местами пару элементов;
- собственно сортирующий алгоритм, который осуществляет сравнение и перестановку элементов до тех пор, пока все элементы множества не будут упорядочены.
М е т о д п у з ы р ь к а ( обменная сортировкой с выбором).
Идея этого метода отражена в его названии. Самые легкие элементы массива "всплывают" наверх, самые "тяжелые" - тонут. Алгоритмически это можно. Реализуется так - будем просматривать весь массив "снизу вверх" и менять стоящие рядом элементы в том случае, если "нижний" элемент меньше, чем "верхний". Таким образом, мы вытолкнем наверх самый "легкий" элемент всего массива. Теперь повторим всю операцию для оставшихся неотсортированными N-1 элементов (т.е. для тех, которые лежат "ниже" первого).
#include <stdio.h>
#define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; }
main()
{
int a[10], dim=10;
int i, j;
for (i=0;i<dim;i++)
{
printf("Элемент\n");
scanf("%d",&a[i]);
}
printf("Было\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
/* Проход массива "вниз", начиная с нулевого элемента */
for (i = 0; i < dim; i++)
/* Проход массива "вверх", начиная с последнего до i-го элемента */
for (j = dim-1; j > i; j--)
/* Сравнение двух соседних элементов и их обмен */
if(a[j-1] > a[j]) swap(a[j-1], a[j]);
printf("Стало\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
}
С о р т и р о в к а в ы б о р о м.
На этот раз при просмотре массива мы будем искать наименьший элемент, сравнивая его с первым. Если такой элемент найден, поменяем его местами с первым. Затем повторим эту операцию, но начнем не с первого элемента, а со второго. И будем продолжать подобным образом, пока не рассортируем весь массив.
#include <stdio.h>
#define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; }
main()
{
int a[10], dim=10;
int i, j, k;
for (i=0;i<dim;i++)
{
printf("Элемент\n");
scanf("%d",&a[i]);
}
printf("Было\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
/* Проход массива, начиная с 0-го до предпоследнего элемента */
for (i = 0; i < dim-1; i++)
{
/* Проход массива, начиная с (i+1)-го до последнего элемента */
for (k = i, j = i+1; j < dim; j++)
if(a[j] < a[k]) k = j; /* Поиск наименьшего k-го эл-та */ swap(a[k], a[i]); /* Перемещение наименьшего "вверх" */
}
printf("Стало\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
}
М е т о д Ш е л л а.
Этот метод предложил Donald Lewis Shell в 1959 г. Основная идея алгоритма заключается в том, чтобы вначале устранить массовый беспорядок в массиве, сравнивая далеко стоящие друг от друга элементы. Как видно, интервал между сравниваемыми элементами (gap) постепенно уменьшается до единицы. Это означает, что на поздних стадиях сортировка сводится просто к перестановкам соседних элементов (если, конечно, такие перестановки являются необходимыми).
#include<stdio.h>
#define swap(a,b) { int tmp; tmp=a; a=b; b=tmp; }
main()
{
int a[10], dim=10;
int i, j, gap;
for (i=0;i<dim;i++)
{
printf("Элемент\n");
scanf("%d",&a[i]);
}
printf("Было\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
for (gap = dim/2; gap > 0; gap/=2) /* Выбор интервала */
for (i = gap;i < dim; i++) /* Проход массива */
/* Сравнение пар, отстоящих на gap друг от друга */
for (j = i-gap; j >= 0 && a[j] > a[j+gap]; j -= gap) swap(a[j], a[j+gap]);
printf("Стало\n");
for (i=0;i<dim; i++)
printf("%d\n",a[i]);
}
2.2 Рекурсивные алгоритмыФункция называется рекурсивной, если ее значение для данного аргумента определяется через значения той же функции для предшествующих аргументов. В программировании функция называется рекурсивной, если последовательность операторов, составляющих тело функции, включает в себя один или несколько вызовов самой этой функции.
Рассмотрим более подробно организацию и работу рекурсивных подпрограмм.
Рекурсию можно использовать для вычисления факториала n!. Чтобы найти n!, нужно определить (n-1)!. А для этого необходим (n-2)! и так далее.
#include <conio.h>
#include <stdio.h>
int z;
int Fact(int n)
{
if (n == 1) return 1;
else return Fact(n - 1) * n; }
main()
{ int n;
printf("Число? \n");
scanf("%d",&n);
z = Fact(n); printf("%d",z);
}
... . Имеет ли право на существование эта биологизаторская интерпретация экологии? Видимо, да. Она широко представлена, и с этим следует считаться. Но она не может служить концептуальной основой комплексного непрерывного экологического образования. В структуре научного знания при таком подходе не остаётся места для географической и социальной экологии, экологии человека, а сама биология превращается ...
... хотя бы стены, чтобы нас не унижали в собственном доме, до конца не растащили наше имущество, нам нужна, обладающая высоким моральным и воинским духом достойно обеспеченная армия. Однако, значение российской армии и в том, что она представляет собой, пожалуй, единственный институт в современной виртуальной России, лишенный симулякров, поскольку ней, по крайней мере, погибают реально - в бою. ...
... важные функции управления предприятием, такие как: определение задач; планирование ресурсов; оценка деятельности и мотивация персонала на основе оценки; контроль исполнения. В целом, бюджетирование решает тактические вопросы и, по существу, для стратегического управления не предназначено. Связь бюджетирования со стратегией Практика стратегического планирования западных компаний ...
... . Для этого достаточно измерить его на карте и знать масштаб карты. Компас. Научиться пользоваться компасом нетрудно. Но компас, как правило, наилучшим помощником в ориентировании становится вместе с картой. В спортивном ориентировании пользуются специальными жидкостными компасами. Они позволяют быстро и просто взять с карты нужное направление и двигаться по местности по выбранному азимуту. ...
0 комментариев