Типы данных на языке Си. Препроцессор.

Типы данных на языке Си. Препроцессор.

int - целый длиной 2 байта, диапазон значений -32768 ... +32767; short - целый короткий, для IBM PC аналогичен int; long - целый длиной 4 байта, диапазон значений -2*109 ... 2*109 char - символьный длиной 1 байт, его можно рассматривать как целое -128...+127 (иногда 0...255); float - тип данных с плавающей точкой, длиной 4 байта, вещественное число с диапазоном значений от ±8.4·10-37 до ±3.3·1038 и 6-ю значащими цифрами; double - тип данных с плавающей точкой, длиной 8 байт, вещественное число с диапазоном значений от ±2.2·10-308 до ±1.8·10308 и 14-ю значащими цифрами

Препроцессор — это компьютерная программа, принимающая данные на входе и выдающая данные, предназначенные для входа другой программы (например,компилятора). О данных на выходе препроцессора говорят, что они находятся в препроцессированной форме, пригодной для обработки последующими программами (компилятор). Результат и вид обработки зависят от вида препроцессора; так, некоторые препроцессоры могут только выполнить простую текстовую подстановку, другие способны по возможностям сравниться с языками программирования. Наиболее частый случай использования препроцессора — обработка исходного кода перед передачей его на следующий шаг компиляции. Языки программирования C/C++ и система компьютерной вёрстки TeX используют препроцессоры, значительно расширяющие их возможности.

· В некоторых языках программирования этап компиляции и трансляции получили название «препроцессинга».

Структурное программирование. Описание. Достоинства и недостатки. Подходы.

Сутью структурного программирования является возможность разбиения программы на составляющие элементы. Идеи структурного программирования появились в начале 70-годов в компании IBM, в их разработке участвовали известные ученые Э. Дейкстра, Х. Милс, Э. Кнут, С. Хоор. Распространены две методики (стратегии) разработки программ, относящиеся к структурному программированию: программирование "сверху вниз" и программирование "снизу вверх".

Достоинства структурного программирования:

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

2) повышается эффективность программ (структурирование программы позволяет легко находить и корректировать ошибки, а отдельные подпрограммы можно переделывать (модифицировать) независимо от других);

3) уменьшается время и стоимость программной разработки;

4) улучшается читабельность программ.

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

Теорема о структурном программировании:

Любую схему алгоритма можно представить в виде композиции вложенных блоков begin и end, условных операторов if, then, else, циклов с предусловием (while) и может быть дополнительных логических переменных (флагов).
Эта теорема была сформулирована итальянскими математиками К. Бомом и Дж. Якопини в 1966 году и говорит нам о том, как можно избежать использования оператора перехода goto.

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

Разветвляющие программы. Циклы и другие управляющие средства. Примеры.

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

1. Выполнение последовательности операторов.

2. Использование проверки истинности условия для выбора между различными возможными способами действия.

3. Выполнение определенной последовательности операторов до тех пор, пока некоторое условие истинно.

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

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

На языке Си:

while(<условие>){ <тело цикла>}

Цикл с постусловием— цикл, в котором условие проверяется после выполнения тела цикла. Отсюда следует, что тело всегда выполняется хотя бы один раз. В языке Паскаль этот цикл реализует оператор repeat..until; в Си — do…while.
На языке Си:

do{ <тело цикла>}while(<условие продолжения цикла>)

Цикл со счётчиком — цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. В большинстве процедурных языков программирования реализуется оператором for, в котором указывается счётчик (так называемая «переменная цикла»), требуемое количество проходов (или граничное значение счётчика) и, возможно, шаг, с которым изменяется счётчик.

for (int i=0; i<20; i++) {действие}

if, if-else и switch

Оператор break используется для выхода из оператора while, do, for, switch, непосредственно его содержащего.

Оператор continue служит для пропуска оставшейся части выполняемой итерации цикла, непосредственно его содержащего

Статическая память. Указатели. Примеры.

Указатель содержит адрес переменной (объекта), что дает возможность "косвенного" доступа к этой переменной (объекту) через указатель.

Когда за знаком & следует имя переменной, результатом операции является адрес указанной переменной.

Когда за знаком * следует указатель на переменную, результатом операции является величина, помещенная в ячейку с указанным адресом.

int x = 1, y = 2;

int *ptr; // объявили указатель на целую переменную

ptr = &x; // взяли адрес переменной х = 2

y = *ptr; // переменная у стала равной 1

*ptr = 0; // переменная х стала равной 0

Пример:

age = 105;

ptr =&age;/*указатель на age*/

val= *ptr;

Результатом выполнения этого фрагмента является присваивание значения 105 переменной val.


6 Динамическая память. Основные функции. Примеры

Динамическая память – это оперативная память компьютера, предоставляемая программе при ее работе.

Основу системы динамического распределения памяти в Си составляют библиотечные функции calloc(), malloc(), realloc() и free().

Рассмотрим прототипы этих функций.

1. Функцияcalloc()

calloc() – предназначена для выделения памяти под заданное количество объектов, каждый из которых имеет размер size байт, всего n ´ size байт. Возвращает указатель на первый байт выделенной область памяти.

void *calloc(size_t num, size_t size);

Функция malloc()

malloc() – предназначена для выделения непрерывной области памяти заданного размера, например, void * malloc(size_t size),

где size_t – тип результата операции sizeof, определяемый в различных объектах-заголовках

Функция realloc()

realloc() – предназначена для изменения размера динамически выделенной области, адресуемой указателем ptr, при этом размер области может как увеличиваться, так и уменьшаться:

void* realloc(void* ptr, size_t size);

где ptr – указатель на первоначальную область памяти, size – новый размер области памяти.

4. Функция free()

free() – предназначена для освобождения памяти, выделенной функциями malloc(),calloc(),realloc(). После выполнения функции освобожденная память может быть выделена вновь под другие данные:

void free(void* ptr);

где prt – указатель на область памяти, созданной только функциями динамического управления памятью malloc(),calloc(),realloc().

7 Функции. Основные определения. Примеры

Функция — это самостоятельная единица программы, спроектированная для реализации конкретной задачи.

Рекурсия — действие при котором функция вызывает сама себя.

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

#include <stdio.h>

int hello_world();

int main() {

hello_world();

return(0);

}

void hello_world(){

printf("Hello, World!\n");

}

Классы памяти переменных.

Класс памяти переменной — понятие в некоторых языках программирования. Он определяет область видимости переменной, а также как долго переменная находится в памяти.

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

• auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является сокращением слова automatic.

• static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.

• extern - внешний - идентификаторы, называемые внешними, external, используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.

• register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.

Класс памяти можно не указывать, тогда действуют следующие умолчания:

· переменные, описанные внутри функции или блока, считаются локальными (auto)

· переменные, описанные вне всех функций, считаются внешними.

· функции считаются внешними.

Рекурсия. Классификация. Примеры.

В программировании рекурсия — вызов функции (процедуры) из неё же самой, непосредственно (простая рекурсия) или через другие функции (сложная или косвенная рекурсия), например, функция Типы данных на языке Си. Препроцессор. - student2.ru вызывает функцию Типы данных на языке Си. Препроцессор. - student2.ru , а функция Типы данных на языке Си. Препроцессор. - student2.ru — функцию Типы данных на языке Си. Препроцессор. - student2.ru . Количество вложенных вызовов функции или процедуры называется глубиной рекурсии.

Различают прямую и косвенную рекурсию. Прямой (непосредственной) рекурсией является вызов функции внутри тела этой функции. Косвенной рекурсией является рекурсия, осуществляющая рекурсивный вызов функции посредством цепочки вызова других функций. Все функции, входящие в цепочку, тоже считаются рекурсивными.

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

Имеется специальный тип рекурсии, называемый «хвостовой рекурсией». Интерпретаторы и компиляторы функциональных языков программирования, поддерживающие оптимизацию кода (исходного или исполняемого), автоматически преобразуют хвостовую рекурсию к итерации, благодаря чему обеспечивается выполнение алгоритмов с хвостовой рекурсией в ограниченном объёме памяти. Такие рекурсивные вычисления, даже если они формально бесконечны (например, когда с помощью рекурсии организуется работа командного интерпретатора, принимающего команды пользователя), никогда не приводят к исчерпанию памяти.

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

int fact (int n) {

if (n == 0)

return 1;

else

return n * fact (n - 1);

}

Эта исходная функция fact() не имеет хвостовой рекурсии т. к. выполняется перемножение после рекурсивного вызова (умножение на n ). Для исключения рекурсии сначала приведем fact () к виду с хвостовой рекурсией, например

int fact (int n) {

return fact_w (1,n);

}

Вспомогательная функция fact_w () будет содержать хвостовую рекурсию. Ее программный код:

int fact_w (int r, int n) {

if (n == 0)

return r;

else

return fact(r*n,n-1);

}

В функции fact_w() хвостовая рекурсия присутствует в силу рекурсивного обращения только к самой функции в операторе return.

10. Символьные строки. Способы задания строк. Принципы обработки.

Ввод-вывод строк

fgets - прочитать строку из входного потока, включая символ новой строки.

Определение: char *fgets (s, n, stream)

char *s;

int n;

FILE *stream;

gets - прочитать строку из стандартного файла ввода stdin.

Определение: char *gets (s)

char *s;

fputs - записать строку в поток stream.

Определение: int fputs (s, stream)

char *s;

FILE *stream;

puts - записать строку в стандартный файл вывода stdout. В конце строк записывается символ новой строки.

Определение:

int puts (s)

char *s;

Обработка строк

Для выполнения описанных в этом подразделе функций необходимо включить в программу файл string.h командой

strcpy - копировать строку s2 в строку s1.

Определение: char *strcpy(s1,s2)

strncpy - копировать не более n символов строки s2.

Определение: char *strncpy(s1,s2,n)

strcat - сцепить две строки.

Определение: char *strcat(s1,s2)

strncat - сцепить две строки, причем из второй строки копировать не более n символов.

Определение: char *strncat(s1,s2,n)

strlen - определить длину строки (число символов без завершающего нулевого символа).

Определение: int strlen(s)

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

Программные регистры

Регистрами называют участки высокоскоростной памяти, расположенные внутри ЦПУ и предназначенные для оперативного хранения данных и быстрого доступа к ним со стороны внутренних компонентов процессора. Существует 8 регистров общего назначения, 6 сегментных регистров, регистр состояния процессора, или регистр флагов (EFLAGS), и регистр указателя команд (EIP).

Регистры EAX, EBX, ECX, EDX – это регистры общего назначения. Они имеют определённое назначение (так уж сложилось исторически), однако в них можно хранить любую информацию.

Регистры EBP, ESP, ESI, EDI – это также регистры общего назначения. Они имеют уже более конкретное назначение. В них также можно хранить пользовательские данные, но делать это нужно уже более осторожно, чтобы не получить «неожиданный» результат.

В МП 8086/8088 имеется 14 регистров. В функциональном отношении они делятся на группы:

  • регистры общего назначения (АХ, ВХ, СХ, DX); предназначены для хранения операндов и выполнения основных команд; любой из них может использоваться как совокупность двух независящих друг от друга 8-разрядных регистров: старшего байта регистра (АН, ВН, СН, DH) и младшего байта (AL, BL, CL, DL); например, АХ состоит из АН и AL;
  • сегментные регистры (CS, DS, SS, ES); используются для указания сегмента при адресации памяти;
  • регистры-указатели (SP, BP, IP); используются для указания смещения при адресации памяти;
  • индексные регистры (SI, DI); применяются для индексной адресации;
  • регистр флагов; используется для хранения признаков состояния процессора.

Способы адресации к памяти.

Режимы адресации – это различные способы указания местоположения операндов. Операнды чаще всего находились в регистрах или в переменных в памяти. Но в процессоре Intel 8086 существуют также более сложные режимы, которые позволяют организовать работу с массивами, структурами, локальными переменными и указателями.

1. Неявная адресация. Местоположение операнда фиксировано и определяется кодом операции. Примеры:

cbw

mul al

Команда CBW всегда работает с регистрами AX и AL, а у команды MUL фиксировано положение первого множителя и результата. Такой режим адресации делает машинную команду короткой, так как в ней отсутствует указание одного или нескольких операндов.

2. Непосредственная адресация. При непосредственной адресации значение операнда является частью машинной команды. Понятно, что в этом случае операнд представляет собой константу. Примеры:

mov al,5

add bx, 1234h

mov dx,a

Обратите внимание, что в третьей строке в DX помещается адрес метки или переменной a, а вовсе не значение по этому адресу. По сути адрес метки тоже является числовой константой.

3. Абсолютная прямая адресация. В машинной команде содержится адрес операнда, находящегося в памяти. Пример:

mov dx, [a]

Вот тут уже в DX помещается значение из памяти по адресу a. Сравните с предыдущим пунктом. Квадратные скобки обозначают обращение по адресу, указанному внутри этих скобок.

4. Относительная прямая адресация. Этот режим используется в командах передачи управления. В машинной команде содержится смещение, которое прибавляется к значению указателя команд IP. То есть указывается не сам адрес перехода, а на сколько байтов вперёд или назад надо перейти. Пример:

metka: ...

loop metka

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

5. Регистровая адресация. Операнд находится в регистре. Пример:

add ax,bx

6. Косвенная регистровая (базовая) адресация. Адрес операнда находится в одном из регистров BX, SI или DI. Примеры:

add ax,[bx]

mov dl,[si]

Размер операнда в памяти здесь определяется размером первого операнда. Так как AX – 16-разрядный регистр, то из памяти берётся слово по адресу в BX. Так как DL – 8-разрядный регистр, то из памяти берётся байт по адресу в SI. Это правило верно и для других режимов адресации.

7. Косвенная регистровая (базовая) адресация со смещением

Адрес операнда вычисляется как сумма содержимого регистра BX, BP, SI или DI и 8- или 16-разрядного смещения. Примеры:

add ax,[bx+2]

mov dx,[array1+si]

В качестве смещения можно указать число или адрес метки. О размере смещения не беспокойтесь – компилятор сам его определяет и использует нужный формат машинной команды.

8. Косвенная базовая индексная адресация

Адрес операнда вычисляется как сумма содержимого одного из базовых регистров BX или BP и одного из индексных регистров SI или DI. Примеры:

mov ax,[bp+si]

add ax,[bx+di]

Например, в одном из регистров может находиться адрес начала массива в памяти, а в другом – смещение какого-то элемента относительно начала.

9. Косвенная базовая индексная адресация со смещением. Адрес операнда вычисляется как сумма содержимого одного из базовых регистров BX или BP, одного из индексных регистров SI или DI и 8- или 16-разрядного смещения. Примеры:

mov al,[bp+di+5]

mov bl,[array2+bx+si]

Безусловные переходы

Безусловный переход — это переход, который выполняется всегда. Безусловный переход осуществляется с помощью команды JMP. У этой команды один операнд, который может быть непосредственным адресом (меткой), регистром или ячейкой памяти, содержащей адрес. Существуют также «дальние» переходы — между сегментами, однако здесь мы их рассматривать не будем. Примеры безусловных переходов:

jmp metka ;Переход на метку jmp bx ;Переход по адресу в BX jmp word[bx] ;Переход по адресу, содержащемуся в памяти по адресу в BX

Условные переходы

Условный переход осуществляется, если выполняется определённое условие, заданное флагами процессора (кроме одной команды, которая проверяет CX на равенство нулю). Как вы помните, состояние флагов изменяется после выполнения арифметических, логических и некоторых других команд. Если условие не выполняется, то управление переходит к следующей команде.

Существует много команд для различных условных переходов. Также для некоторых команд есть синонимы (например, JZ и JE — это одно и то же).

Команда Переход, если Условие перехода
JZ/JE нуль или равно ZF=1
JNZ/JNE не нуль или не равно ZF=0
JC/JNAE/JB есть переполнение/не выше и не равно/ниже CF=1
JNC/JAE/JNB нет переполнения/выше или равно/не ниже CF=0
JP число единичных бит чётное PF=1
JNP число единичных бит нечётное PF=0
JS знак равен 1 SF=1
JNS знак равен 0 SF=0
JO есть переполнение OF=1
JNO нет переполнения OF=0
JA/JNBE выше/не ниже и не равно CF=0 и ZF=0
JNA/JBE не выше/ниже или равно CF=1 или ZF=1
JG/JNLE больше/не меньше и не равно ZF=0 и SF=OF
JGE/JNL больше или равно/не меньше SF=OF
JL/JNGE меньше/не больше и не равно SF≠OF
JLE/JNG меньше или равно/не больше ZF=1 или SF≠OF
JCXZ содержимое CX равно нулю CX=0

У всех этих команд один операнд — имя метки для перехода. Обратите внимание, что некоторые команды применяются для беззнаковых чисел, а другие — для чисел со знаком. Сравнения «выше» и «ниже» относятся к беззнаковым числам, а «больше» и «меньше» — к числам со знаком. Для беззнаковых чисел признаком переполнения будет флаг CF, а соответствующими командами перехода JC и JNC. Для чисел со знаком о переполнении можно судить по состоянию флага OF, поэтому им соответствуют команды перехода JO и JNO. Команды переходов не изменяют значения флагов.

Dim dw 0011h,2233h,4455h,6677h,8899h

Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw, то есть она равна 2 байта. Чтобы получить доступ к числу 6677h, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
В общем случае для получения адреса элемента в массиве необходимо начальный (базовый) адрес массива сложить с произведением индекса этого элемента на размер элемента массива:

база + (индекс*размер элемента)

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

Двумерный массив

Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i, j) вычисляется по формуле

(база + количество_элементов_в_строке * размер_элемента * i+j)

Здесь i = 0...n–1 указывает номер строки, а j = 0...m–1 указывает номер столбца.

Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 на 4
(i= 0...3, j = 0...3):

23 04 05 67
05 06 07 99
67 08 09 23
87 09 00 08

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

23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08

Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент
mas(2, 3) = 23, то проведя нехитрый подсчет, убедимся в правильности наших рассуждений:

Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11

Прочие алгоритмы сортировки

1).model tiny,STDCALL

.data

array dw 1, 4,7,-2,10,24,-100,2,101

len equ ($-array)/2

.code

.startup

lea ax,array

mov cx,len

call sort,ax,cx

.exit 0

2) ;сортировка строки методом "пузырька" по убыванию,

;массив ar, длина cnt

sort proc ar:word, cnt:word

uses si,di,cx,bx ;сохраним используемые регистры в стеке

mov bx,ar ;адрес массива

xor si,si ;индекс в массиве

mov cx,cnt ;длина масива

jcxz sort_ret ;проверим на пустой массив

dec cx ;будем проверять до предпоследнего включительно

jcxz sort_ret ;проверим, что больше одного элемента

loop1: ;цикл по (первому)элементу, который сравнивается

push cx ;сохраним количество

mov ax,[bx+si] ;берем первое значение и сравниваем со всеми последующими

lea di,[si+2] ;смещение, с чем сравниваем

;сравниваем cx раз (от последующего до последнего)

loop2:

call cmpmod,[bx+di] ;сравним ax и слово по адресу [bx+di]

jge next ;на следующий, если ax=[bx+si]>=[bx+di]

xchg ax,[bx+di] ;меняем местами arr1[si] и arr1[di]

mov [bx+si],ax ;причем в ax будет новый первый(максимальный)элемент

next:

lea di,[di+2] ;на следующий элемент

loop loop2 ;на внутренний цикл

pop cx ;восстановим счетчик цикла по первым элементам

lea si,[si+2] ;на следующий первый элемент

loop loop1 ;на внешний цикл

sort_ret:

ret

sort endp

3) ;сравнение модуля числа из регистра ax и аргумента arg

;результат возвращается во флагах

;Важно, чтобы не использовалась модель передачи параметров C,

;в которой параметры убираются из стека командой add sp,<число>

;которая, в свою очередь, портит флаги.

;Или возвращать результат в виде числа -1,0,1

cmpmod proc arg:word

uses ax,dx ;сохраним регистры

test ax,ax ;проверим на знак

jns test_second ;положительное - обходим

neg ax ;меняем знак

test_second:

mov dx,arg ;вторая величина

test dx,dx ;проверим на знак

jns compare_mod ;положительное - обходим

neg dx ;меняем знак

compare_mod:

cmp ax,dx ;сравниваем, результат во флагах

ret

cmpmod endp

end

Команды работы со строками. Примеры.

Команды обработки строковых примитивов

В системе команд процессоров Intel предусмотрено пять групп команд для обработки массивов байтов, слов и двойных слов (таблица). Для адресации памяти в командах, приведенных в таблице, используется регистр ESI, EDI или сразу оба этих регистра.

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

Команда Название Описание
MOVSB,MOVSW, MOVSD Move String data, или Переместить строку данных Копирует целочисленные данные из одного участка памяти в другой
CMPSB, CMPSW, CMPSD Compare strings, или Сравнить строки Сравнивает значение двух участков памяти произвольной длины
SCASB,SCASW, SCASD Scan string, или Сканировать строку Сравнивает целочисленное значение с содержимым участка памяти
STOSB, STOSW, STOSD Store String data, или Сохранить строковые данные Записывает целочисленное значение в участок памяти произвольной длины
LODSB, LODSW, LODSD Load accumulator from string, или Загрузить строковые данные Загружает целочисленное значение из памяти в аккумулятор (регистр AL, АХ или ЕАХ)

Команды MOVSB, MOVSW и MOVSD

Пример: копирование массива двойных слов. Предположим, что нам нужно скопировать массив, состоящий из двадцати двойных слов из переменной source в переменную target. После копирования данных, в регистрах ESI и EDI будут находиться значения, соответствующие адресам 21-го элемента массивов source и target (если бы они существовали):

segment .data

source times 10 DD 0xFFFFFFFF

msglen equ $-source

target times 10 DD 0

segment .text

cld ;Сбросим флаг DF и установим прямое направление

mov ecx,LENGTHOF source ;Зададим значение счетчика

mov esi, source ;Зададим адрес источника данных

mov edi, target ; Зададим адрес получателя данных

rep movsd ; Копируем 20 двойных слов

Команды CMPSB, CMPSW и CMPSD

Эти команды позволяют сравнить данные из одного участка памяти, адрес которого указан в регистре ESI, с другим участком памяти, адрес которого указан в регистре EDI.

С командами CMPSB, CMPSW и CMPSD может использоваться префикс повторения.

Пример, Предположим, что мы хотим сравнить значения двух двойных слов, расположенных в памяти, с помощью команды CMPSD. В приведенном ниже фрагменте кода можно заметить, что исходный операнд (переменная source) меньше операнда получателя данных (переменная target). Поэтому при выполнении команды JA не произойдет переход программы на метку L1, а будет выполнена следующая за ней команда JMР:

segment .data

source DD 1234h

target DD 5678h

segment .data

mov esi, source

mov edi, target

cmpsd ; Сравним двойные слова

ja LI ; Перейдем, если source > target

jmp L2 ; Перейдем, если source <= target

Если же мы хотим сравнить между собой несколько двойных слов, нам нужно сбросить флаг направления DF, загрузить в регистр ECX счетчик повторения и поместить перед командой CMPSD префикс повторения REPE:

mov esi, source

mov edi, target

cld ;Направление сравнения восходящее

mov ecx, [LENGTHsource] ; Загрузим счетчик повторения

repe cmpsd ;Повторим сравнение, пока операнды равны

Команды SCASB, SCASW и SCASD

Эти команды сравнивают значение, находящееся в регистрах AL/AX/EAX с байтом, словом или двойным словом, адресуемым через регистр EDI. Данная группа команд обычно используется при поиске какого-либо значения в длинной строке или массиве. Если перед командой SCAS поместить префикс REPE (или REP), строка или массив будет сканироваться до тех пор, пока значение в регистре ECX не станет равным нулю, либо пока не будет найдено значение в строке или массиве, отличное от того, что находится в регистре AL/AX/EAX (т.е. пока не будет сброшен флаг нуля ZF). При использовании префикса REPNE, строка или массив будет сканироваться до тех пор, пока значение в регистре ECX не станет равным нулю, либо пока не будет найдено значение в строке или массиве, совпадающее с тем, что находится в регистре AL/AX/EAX (т.е. пока не будет установлен флаг нуля ZF).

Пример. Поиск символов в строке, В приведенном ниже фрагменте кода выполняется поиск символа F в строке alpha. При нахождении данного символа, в регистре EDI будет содержаться его адрес плюс единица. Если же искомого символа нет в исходной строке, то работа программы завершается в результате перехода по команде JNZ:

SECTION.data

alpha db "ABCDEFGH",0

msglen equ $-alpha

SECTION .text

_main:

mov edi, alpha ;Загрузим в EDI адрес строки alpha

mov al,'F' ; Загрузим в AL ASCII-код символа "F"

mov ecx, msglen ; Загрузим в ECX длину строки alpha

cld ;Направление сравнения восходящее

repne scasb ; Сканируем строку пока не найдем символ "F"

jnz quit ; Если не нашли, завершим работу

dec edi ;Здесь символ найден, в EDI его адрес

Команды STOSB, STOSW и STOSD

Эта группа команд позволяет сохранить содержимое регистра AL/AX/EAX в памяти, адресуемой через регистр EDI. При выполнении команды STOS содержимое регистра EDI изменяется в соответствии со значением флага направления DF и типом используемого в команде операнда. При использовании совместно с префиксом REP, с помощью команды STOS можно записать одно и то же значение во все элементы массива или строки. Например, в приведенном ниже фрагменте кода выполняется инициализация строки stringl значением 0FFh:

SECTION .data

count dd 2

stringl times 11 db 0

SECTION .text

_main:

mov al, 0x61 ; Записываемое значение

mov edi, stringl ; Загрузим в EDI адрес строки

mov ecx, [count] ; Загрузим в ECX длину строки

cld ; Направление сравнения восходящее

rep stosb ;Заполним строку содержимым AL

Команды LODSB, LODSW и LODSD

Эта группа команд позволяет загрузить в регистр AL/AX/EAX содержимое байта, слова или двойного слова памяти, адресуемого через регистр ESI. Префикс REP практически никогда не используется с командой LODS. Таким образом, эта команда используется для загрузки одного значения в аккумулятор. Например, команду LODSB можно использовать вместо двух приведенных ниже команд (если флаг направления DF не установлен):

mov al,[esi] ;Загрузим байт в AL

inc esi ; Адрес следующего байта

Умножение элементов массива, В приведенной ниже программе каждый элемент массива двойных слов array умножается на постоянное значение. Для загрузки в регистр EAX текущего элемента массива используется команда LODSD, а для сохранения — STOSD.

extern _printf

GLOBAL _main

SECTION .data

array dd 1,2,3,4,5,6,7,8,9,10

multiplier db 2

fp db "%d, ", 0

SECTION .text

_main:

cld ; Направление -- восходящее

mov esi, array ; Загрузим адрес массива в регистры ESI и EDI

mov edi, esi

mov ecx, 10 ; Загрузим длину массива

L1:

lodsd ;Загрузим текущий элемент массива

mul byte [multiplier] ;Умножим его на константу

stosd ; Запишем EAX в текущий элемент

loop L1 ;массива (его адрес в [EDI])

mov esi, array

mov ecx, 10

cyc:

push ecx

lodsd

push eax

push fp

call _printf

add esp, 8

pop ecx

loop cyc

mov ax, 0

ret

20. Основные конструкции языка. AT&T-синтаксис.

Основы смешанного программирования

Включение файла: #include

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

assert.h - диагностика программ

errno.h - проверка ошибок

limits.h - предельные значения целочисленных данных

locale.h - поддержка национальной среды

math.h - математические вычисления

signal.h - обработка исключительных ситуаций

stddef.h - дополнительные определения

stdio.h - средства ввода-вывода

stdlib.h - функции общего назначения (работа с памятью)

string.h - работа со строками символов

time.h - определение дат и времени

В конкретных реализациях количество и наименование заголовочных файлов могут быть и другими. Например, в компиляторах для MS-DOS активно используются заголовочные файлы mem.h, alloc.h, conio.h, dos.h и другие. В компиляторах Turbo C, Borland C++ для связи с графической библиотекой применяется заголовочный файл graphics.h.

Принципы создания текстового меню. Примеры.

Файловый ввод-вывод в Си.

  Потоковая функция или макрокоманда Назначение
fopen Открывает поток для чтения и (или) записи
fclose Закрывает поток
fread Читает блок данных из потока
fgets Читает строку текста из потока
fscanf Читает форматированные данные из потока
fwrite Записывает блок данных в поток
fputs Записывает строку текста в поток
fprintf Записывает форматированные данные в поток
fseek Перемещает указатель чтения или записи в потоке
ftell Возвращает текущую позицию в потоке, начиная с которой будет выполнена следующая операция чтения или записи. Возвращаемое значение - это количество байтов смещения относительно начала потока
freopen Повторно использует указатель потока для ссылки на новый файл
fdopen Открывает потоковый файл с указанным дескриптором
feof Макр

Наши рекомендации