Типы данных на языке Си. Препроцессор.
Типы данных на языке Си. Препроцессор.
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.
На языке Си:
Цикл со счётчиком — цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. В большинстве процедурных языков программирования реализуется оператором 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)
· переменные, описанные вне всех функций, считаются внешними.
· функции считаются внешними.
Рекурсия. Классификация. Примеры.
В программировании рекурсия — вызов функции (процедуры) из неё же самой, непосредственно (простая рекурсия) или через другие функции (сложная или косвенная рекурсия), например, функция вызывает функцию , а функция — функцию . Количество вложенных вызовов функции или процедуры называется глубиной рекурсии.
Различают прямую и косвенную рекурсию. Прямой (непосредственной) рекурсией является вызов функции внутри тела этой функции. Косвенной рекурсией является рекурсия, осуществляющая рекурсивный вызов функции посредством цепочки вызова других функций. Все функции, входящие в цепочку, тоже считаются рекурсивными.
Преимущество рекурсивного определения объекта заключается в том, что такое конечное определение теоретически способно описывать бесконечно большое число объектов. С помощью рекурсивной программы же возможно описать бесконечное вычисление, причём без явных повторений частей программы.
Имеется специальный тип рекурсии, называемый «хвостовой рекурсией». Интерпретаторы и компиляторы функциональных языков программирования, поддерживающие оптимизацию кода (исходного или исполняемого), автоматически преобразуют хвостовую рекурсию к итерации, благодаря чему обеспечивается выполнение алгоритмов с хвостовой рекурсией в ограниченном объёме памяти. Такие рекурсивные вычисления, даже если они формально бесконечны (например, когда с помощью рекурсии организуется работа командного интерпретатора, принимающего команды пользователя), никогда не приводят к исчерпанию памяти.
Рассмотрим пример вычисления факториала целого положительного числа, когда есть, и нет хвостовой рекурсии. При этом покажем возможность исключения рекурсии. Пусть определена следующая рекурсивная функция:
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 | Макр Наши рекомендации
|