int integ;
puts("Введите, пожалуйста, целое");
gets(ch);
integ=atoi(ch);
printf("Число было %d.\n", integ);
}
1.11.3 Функции, работающие со строками
strlen()- находит длину строки;
strcat()- объединяет две строки;
strcmp()- сравнивает содержимое строк;
strcpy()- копирует строки.
Рассмотримпример.
В данном примере функция strlen(k) определяет длину строки k.
Функция strcat(k,l) присоединяет к концу строки k строку l.
1.12 Особенности программирования на языке Си
1.12.1 Модули. Многомодульное программирование
Можулем будем называть часть программы помещенная в отдельный файл и транслируемая независимо от других частей программы. Т.о. часть программы подключаемая при помощи препроцессорной команды include, согласно нашему определению, модулем являтся не будет. Напомним читателю некоторые стандартные положения трансляции программ:
1. Стандартный процесс трансляции состоит из двух этапов: собственно трансляции и редактирования связей.
2. На первой стадии трансляции текстовый файл, содержащий фрагмент программы преобразуется к объектному виду.
3. На второй стадии трансляции все объектные модули (в том числе модули, содержащиеся в стандартных и других библиотеках) объединяются в один исполняемый модуль. На этой стадии происходит согласование используемых внешних переменных и внешних функций.
На первый взгляд кажется, что двухэтапный процесс трансляции усложняет и замедляет разработку программного обеспечения. Однако, разбивая программу на модули мы можем перевести их в объектный формат и далее подсоединять их только на втором этапе трансляции, что, в конечном итоге, ускоряет процесс трансляции. Кстати, все стандартные функции, которыми Вы пользуетесь в своих программах содержаться, в стандартных библиотеках объектных модулей. Описание же библиотечных функций содержится в H-файлах. Заметим, что при любом количестве модулей один и только один будет содержать функцию main. Этот модуль мы будем называть главным.
Современные средства трансляции позволяют легко разрабатывать многомодульные программы. Работая с Borland С вы можете либо включить все модули в проект (если Вы работаете с интегрированной средой) либо указать их в командном файле (если Вы работаете ) со строковым компилятором. При этом на первом месте должен стоять главный модуль. При этом, если Вы указали модуль с расширением .obj, то он будет учавствовать только во второй стадии трансляции, в противном случае он каждый раз будет перекомпилироваться. Стандартные библиотеки в проекте указывать не надо - система автоматически подключает их на втором этапе трансляции.
При разработке программного обеспечения разбиение на модули осуществляется тематически. Например, в один модуль помещаются все функции работающие с файлами, во второй - функции, осуществляющие математические расчеты и т.д.
Заканчивая общетеоретическое рассмотрение модульного программирования заметим, что в нем имеется еще один положительный момент. Дело в том, что двух-этапный процесс трансляции и структура объектных файлов являются стандартом для многих трансляторов с языков высокого уровня, а также ассемблеров. Т.о. появляется возможность собирать программы, модули которых были написаны на разных языках. Ниже нами будет приведен пример использования модулей, написанных на языке ассемблера.
Приведем пример двухмодульной программы.
/*Модуль 1, главный*/
#include <stdio.h>
extern int min(int, int, int); /*находит минимальное значение из 3*/
extern int max(int, int, int); /*находит максималное значение из 3*/
void main ()
{
int a,b,c;
a=2; b=10; c=14;
/*напечатать произведение минимального и максимального числа*/
printf("%d\n", max(a,b,c)*min(a,b,c)); /*ответ 28*/
}
/*Модуль 2*/
#include <stdio.h>
externintmin(int, int, int); //находит минимальное значение из 3
extern int max(int, int, int); //находит максималное значение из 3
int max(int a1, int b1, int c1)
{
if(a1>b1)
{
if(c1>a1)return c1; else return a1;
}
else
{
if(b1>c1)return b1; else return c1;
}
}
int min(int a1, int b1, int c1)
{
if(a1<b1)
{
if(c1<a1)return c1; else return a1;
}
else
{
if(b1<c1)return b1;
else return c1;
}
}
Кратко прокоментируем приведенную выше программу.
Программа состоит из двух модулей. В главном модуле содержаться вызовы функций, которые содержаться во втором модуле. Как видно из текста программы при описании функций мы используем ключевое слово extern. Пусть первая программа называется modul1, а вторая modul1. Тогда после первого этапа трансляции на диске появятся объектные модули modul1.obj и modul2.obj. На втором этапе трансляции происходит объединение этих модулей и на диске появляется исполняемый модуль modul1.exe.
1.12.2 Модели памяти
Обратимся теперь к одной весьма важной проблеме, с которой рано или поздно сталкивается любой программист программирующий на Си в операционной системе MSDOS. Эта проблема называется - выбор модели памяти. Выбор модели памяти можно осуществить отметив соответствующие опции в интегрированной среде обработки или или указав соответствующие параметры для строкового компилятора. Вопрос заключается в том: что означает выбранная модель памяти.
Чтобы разобраться в указанной проблеме прежде всего следует обратиться к тому как осуществляется адресация памяти на компьютерах IBM. Прежде всего заметим, что микропроцессор Intel может работать в двух режимах реальном и защищенном, отличающихся в том числе и системой адресации. Операционная система MSDOS работает в реальном режиме (или в имитированном реальном режиме). Начнем, поэтому, с адресации в реальном режиме.
Поскольку первые микропроцессоры Intel были 16-битные то для того, чтобы расширить объем адресуемой памяти, адрес ячейки памяти формируется из двух компонент. Обозначим первую компоненту как seg и назовем ее сегментным адресом. Вторую компоненту обозначим ofs и назовем ее смещением. Тогда физический адрес ячейки может быть найден по формуле: seg*16+ofs. Поскольку размер регистров составляет 16 бит, то имеем, что максимальный возможный адрес составляет (2^16-1)*16+(2^16-1). Т.е. объем охватываемой памяти оказывается равным приблизительно 1 Мб.
Заметим, что при фиксированном seg смещение позволяет адресовать 64 Кб памяти. В результате вся память разбивается на сегменты. Максимальный размер сегмента составляет 64 Кб. Адрес начала сегмента всегда кратен 16. Такую сегментную структуру приходится учитывать и при написании программы. Традиционно в программе можно выделить три компонента: код, данные, стек. Для каждой из этих компонент должно быть выделено определенное количество сегментов.
Для сегментации сегментов в микропроцессоре Intel существует 4 регистра называемых сегментными: cs - регистр сегмента кода, ds - регистр сегмента данных, ss - регистр сегмента стека, es - дополнительный сегментный регистр.
Ранее было введено понятие указателя. До сих пор мы пользовались этим понятием не задумываясь о их типе. Этот тип устанавливается по умолчанию согласно модели памяти (см. ниже). В Си существует 3 типа указателя: NEAR, FAR и HUGE.
Указатель NEAR - соответствует смещению в текущем сегменте. Длина его 16 бит.
Указатель FAR - 32-битный указатель, точнее пара SEG,OFS. Легко видеть, что один и тот же физический адрес может быть представлен несколькими парами: seg,ofs. Кроме того сравнение и действия над указателями касается только смещения. Т.о. два указателя относящиеся к одной и той же ячейки памяти оказываются не равными друг другу.
Указатель HUGE - 32-битный указатель. Отличается от FAR тем, что он нормализован - из всех пар seg,ofs выбрана пара с минимальным ofs. Такой вид указателя называется нормализованным.
Перейдем теперь непосредственно к рассмотрению моделей памяти, коих существует ровно 6.
Модель tiny (крохотная). Все сегментные регистры (cs,ds,ss,es) указывают на один адрес. Т.о. у Вас для всего (кода, данных, стека) всего 64 Кб. памяти. Используются только указатели типа near. Программы написанные в этой модели могут быть преобразованы к COM-виду.
Модель small (малая). Программный сегмент и сегмент данных начинаются с разных адресов. Т.е. для кода и для стека отводится по 64 Кб. Сегмент стека начинается с того же адреса, что сегмент данных. Используются только указатели near.
Модель medium (средняя). Может иметь несколько сегментов кода, но один сегмент данных. Другими словами указатели типа far определены только для сегментов кода. Сегмент стека начинается с адреса сегмента данных.
Модель compact (компактная). Может иметь несколько сегментов данных (один для статических данных), но один сегмент кода. Другими словами указатели типа far определены для сегментов данных (и стека). Стек имеет свой собственный сегмент.
Модель large (большая). Может иметь несколько сегментов кода и данных. Для стека, как и в предыдущем случае, имеется свой сегмент. Используется указатель типа far. Для статических данных отводится один сегмент.
Модель huge (огромная). Совпадает с предыдущей, но снимается ограничение на количество сегментов для статических данных.
Предыдущий материал показывает какие указатели используются по умолчанию в той или иной модели памяти. Однако используя модификаторы near, far и huge можно изменить тип указателя задаваемого по умолчани.
1.12.3 Программирование в DOS и Windows
До сих пор мы не акцентировали Ваше внимание на то в какой операционной системе мы работаем. Для начала программирования на Си это не имеет большого значения. Однако теперь мы можем сказать, что настоящее программирование в Windows начнется только с Главы 4. В данном параграфе мы поговорим о принципиальных особенностях программирования в среде MSDOS и Windows.
Значительная часть времени в программировании уходит на про-граммирование внешних устройств. Причем под внешними устройствами понимается и работа с памятью, файловой системой, дисплеем, клавиатурой, мышью и т.д. Основным отличием операционной системы Windows от MSDOS является то, все управление всеми внешними устройствами Windows берет на себе. Ниже на рисунке представлена схема взаимодействия приложения с внешними устройствами в системах MSDOS и Windows