Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Использование языка ассемблера в программах на Си

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

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

Рассмотрим в начале встроенный ассемблер. Достоинством его является возможность писать ассемблерные фрагменты прямо среди фрагментов на языке Си. Основным недостатком является то, что часто встроенный ассемблер обладает меньшими возможностями реального ассемблера (отсутсвие некоторых команд, директив).

Основой встроенного ассемблера является ключевое слово 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. Примеры использования языка Си

Сортировка

 

Практически каждый алгоритм сортировки можно разбить на три части:

- сравнение, определяющее упорядоченность пары элементов;

- перестановку, меняющую местами пару элементов;

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

М е т о д п у з ы р ь к а (обменная сортировкой с выбором).

Идея этого метода отражена в его названии. Самые легкие элементы массива "всплывают" наверх, самые "тяжелые" - тонут. Алгоритмически это можно. Реализуется так - будем просматривать весь массив "снизу вверх" и менять стоящие рядом элементы в том случае, если "нижний" элемент меньше, чем "верхний". Таким образом, мы вытолкнем наверх самый "легкий" элемент всего массива. Теперь повторим всю операцию для оставшихся неотсортированными 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]);

}

Рекурсивные алгоритмы

 

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

Рассмотрим более подробно организацию и работу рекурсивных подпрограмм.

Рекурсию можно использовать для вычисления факториала 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);

}

 

Задача Ханойские башни

 

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

Программа:

#include <stdio.h>

#include <dos.h> /* sleep() */

#define MAX_NRINGS 64 /* Максимальное число колец */

int st[4][MAX_NRINGS]; /* 1,2,3 - стержни */

int nr[4]; /* Число колец на стержнях */

int nmoves; /* Число перемещений */

/* ---------------------------------------------- */

/* Печать текущего расположения колец на стержнях */

/* ---------------------------------------------- */

void print_st(void)

{

int i, j;

for(i = 1; i <= 3; i++) {

printf("\n| ");

for(j = 0; j < nr[i]; j++) printf("%d ", st[i][j]);

}

printf("\n");

}

/* ------------------------------------ */

/* Установка начального положения колец */

/* ------------------------------------ */

void init(int nrings)

{

for(nr[1] = 0; nrings > 0; nr[1]++,nrings--)

st[1][nr[1]] = nrings;

* Нанизали кольца на 1-й стержень */

nr[2] = nr[3] = 0;

/* Два других стержня пусты */

nmoves = 0;

print_st();

}

/* ----------------------------- */

/* Функция переносит одно кольцо */

/* со стержня n1 на стержень n2 */

/* ----------------------------- */

void move1(int n1, int n2)

{

st[n2][nr[n2]++] = st[n1][--nr[n1]];

sleep(1); /* Пауза в 1 секунду */

print_st(); /* Печать текущей позиции */

nmoves++;

}

/* ------------------------------------------------- */

/* Функция hanoi перемещает верхние nrings колец */

/* со стержня i1 на стержень i3, используя стержень */

/* i2 в качестве промежуточного. 1 <= i1,i2,i3 <= 3. */

/* ------------------------------------------------- */

void hanoi(int nrings, int i1, int i2, int i3)

{

if(nrings == 1)

move1(i1, i3);

else {

hanoi(nrings-1, i1, i3, i2);

move1(i1, i3);

hanoi(nrings-1, i2, i1, i3);

}

}

main()

{

int nrings;

printf("Число колец: "); scanf("%d", &nrings); init(nrings);

hanoi(nrings, 1, 2, 3);

printf("Перенос колец завершен.\n");

printf("Число перемещений - %d.\n", nmoves); return(0);

 }

В прложении N1 показано решение некоторых задачи на языке С.


Глава 3. Основы С++

3.1 Отличия С++ от С

 

1. В С++ ключевое слово void не обязательно (эквивалентно int m(); и int m(void)).

2. В С++ все функции должны иметь прототипы.

3.Если в С++ функция возвращает тип, отличный от void, то оператор return должен содержать значение типа.

4.В С++ можно выбирать место для объявления локальных переменных не только в начале блока.

5.В С++ ввод-вывод может осуществляться не только с помощью функций, но и с помощью операций.

 

3.2 Объектно-ориентированное программирование в С++

 

Развитие средств вычислительной техники требовало новых методик программирования:

- программирование небольших программ на базе переключателей;

- программирование на ассемблере;

- программирование на языках высокого уровня (Фортран);

- программирование на языках структурного программирования (Паскаль, Си);

- объектно-ориентированное программирование (ООП).

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

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

Понятие объекта тесно связано с понятием класса. Класс – это дальнейшее развитие понятия структуры. Он позволяет создавать новые типы и определять функции, манипулирующие с этими типами.

Объект - это представитель определенного класса.

ООП использует механизмы инкапсуляции, полиморфизма и наследования.

Инкапсуляция позволяет создавать объекты - данные, процедуры и функции, манипулирующие с этими данными.

Данные, доступные для использования внутри объекта - private, данные доступные извне - public.

В общем, виде объект можно рассматривать как переменную, определенную программистом.

Полиморфизм позволяет одно имя функции использовать для решения разных задач (общих для класса действий).

В зависимости от данных выполняются те или иные действия.

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

 

Классы

Класс используется для создания объектов. Основная форма имеет вид:

class имя класса

{

закрытые функции и переменные

public:

открытые функции, функции-члены и переменные

}

список объектов;//не является обязательным

Закрытые функции и переменные - члены(members) доступны только для других членов этого класса.

Открытые функции и переменные доступны для любой части программы, в которой находится класс.

Функции, объявленные внутри описания класса называются функциями членами (member functions).

Для определения функций-членов используется форма:

тип имя класса:: имя функции-члена (параметры)

{

тело функции

}

Два двоеточия после имени класса называются операцией расширения области видимости (scope resolution operator).

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

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

Пример.

#include <iostream.h>

class class1 {//объвлен сласс class1

    int a; //доступна для функций членов class1

    public:

    int kwadrat(int b);//функция член класса class1

};

int class1::kwadrat(int b) //определение функции kwadrat()

{

    a=b*b;

    return a;

}

main()

{

class1 c; //создается объект с типа class1

    cout<<"\n"<<c.kwadrat(3)<<"\n";//вычисление и вывод квадрата трех

    return 0;

}

 

Перегрузка функций

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

Пример.

#include <iostream.h>

void k(int a);//прототип первой функции

void k(int a, float b); //прототип второй функции

void k(int a) //описание первой функции

{

    cout << a <<"\n";

}

void k(int a, float b) //описание второй функции

{

    cout <<a<<"\n"<< b <<"\n";

}

main()

{

    k(4);//вызов первой функции

    k(5, 10.2);//вызов второй функции

    return 0;

}

 

Конструкторы

Для автоматической инициализации создаваемых объектов в С++ используется функция - конструктор (constructor function), которая включается в описание класса.

Функция конструктор имеет тоже имя, что и класс и не возвращает ни какого значения.

Пример:

#include <iostream.h>

// Объявление класса class1

class class1 {

    int a;

    public:

    class1(); // Конструктор

    void kwadrat();

};

// Инициализация а конструктором при создании объекта pr

class1::class1()

{

    a=100;

}

//Функция возведения в квадрат и печати

void class1::kwadrat()

{

    cout << a*a;

}

main()

{

    class1 pr;//Создание объекта pr

    pr.kwadrat(); //Вызов функции kwadrat

return 0;

}

Как видно из примера конструктор вызывается при создании объекта pr.

 

Деструкторы

Функция деструктор (destructor)вызывается при удалении объекта для освобождения ресурсов (памяти и т.д.). Она также включается в объявление класса. Перед описанием деструктора ставится значок ~.

Пример.

#include <iostream.h>

// Объявление класса class1

class class1 {

    int a;

    public:

    class1(); // Конструктор

    ~class1(); //Деструктор

    void kwadrat();

};

// Инициализация а конструктором при создании объекта pr

class1::class1()

{

    a=100;

}

//Освобождение ресурсов деструктором

class1::~class1()

{

    cout<<"Освобождение\n";

}

//Функция возведения в квадрат и печати

void class1::kwadrat()

{

    cout << a*a<<"\n";

}

 

ain()

class1 pr;//Создание объекта pr

    pr.kwadrat(); //Вызов функции kwadrat

return 0;

}

Деструктор вызывается при удалении объекта или выхода из программы.

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...