fullcircle:fcm-ru-20:программа_на_си_ч4 Сравнение версий

Различия

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

Ссылка на это сравнение

Следующая версия
Предыдущая версия
fullcircle:fcm-ru-20:программа_на_си_ч4 [2010/03/30 20:31]
создано
— (текущий)
Строка 1: Строка 1:
-======HOW-TO====== 
-=====Програма на Си. Часть 4===== 
-===Автор:​ Эли Дэ Брувэр (Elie De Brauwer)=== 
  
-В четвертой статье я расскажу о важной теме, для каждого Си програм-миста,​ потому что она может причинить множество неприят-ностей:​ динамическое выделе-ние памяти (dynamic memory allocation). Непонимание этого механизма (и указателей) может привести к утечкам памяти и ошибкам в приложе-нии (например,​ к ошибке Segmantation Fault). 
- 
-Сейчас пора каникул,​ и поэтому примером будет приложение,​ которое создаёт ASCII-снег. Чтобы создать этот эффект,​ я буду ис-пользовать небольшую часть биб-лиотеки,​ называемой "​ncurses"​. За дополнительной информацией о библиотеке я советую посетить [[http://​tldp.org/​HOWTO/​NCURSES-Programming-HOWTO|http://​tldp.org/​HOWTO/​NCURSES-Programming-HOWTO]],​ потому что я буду говорить только о функциях,​ которые используются в приложении. 
- 
-===Использование ncurses=== 
-Для использования библиотеки сначала необходимо установить пакеты ncurses: 
-<​code>​apt-get install libncurses5 libncurses5-dev</​code>​ 
-Затем в начало исходного файла нужно добавить заголовок ncurses, дописав **#include <​ncurses.h>​**. Новым является то, что ncurses предоставляется в виде динамической библиотеки,​ что означает две вещи: во-первых,​ компоновщик должен связать наш исходный код с библиотекой ncurses. Это делается такой командой:​ 
-<​code>​gcc -Wall -lncurses snow.c -o snow</​code>​ 
-Параметр **-l** инструктирует компоновщик добавить разделяемую библиотеку ncurses. И в результате этого мы увидим такой вывод: 
-<​code>​edb@lapedb:​~/​fullcircle/​c-4$ ldd snow* 
- linux-gate.so.1 =>  (0xb805c000)* 
- libncurses.so.5 => /​lib/​libncurses.so.5 (0xb7ff7000)* 
- libc.so.6 => /​lib/​tls/​i686/​cmov/​libc.so.6 (0xb7e99000)* 
- libdl.so.2 => /​lib/​tls/​i686/​cmov/​libdl.so.2 (0xb7e94000)* 
- /​lib/​ld-linux.so.2 (0xb8042000)</​code>​ 
- 
-Запуск ldd показывает,​ что приложению требуется наличие в нашей системе библиотеки libncurses.so.5. Ещё это значит,​ что не удастся запустить этот бинарный файл в системе,​ где нет этой библиотеки. 
-Что может ncurses? На самом деле, текстовый терминал - это странная вещь. Используя printf(), можно вывести текст, но он всегда появляется в конце строки. Нельзя перематывать назад, использовать цвета, печатать жирные символы и т.д. Также существуют "​управляющие последователь-ности"​ (escape sequences), которые влияют на поведение курсора и вывод текста в таких терминалах (они существуют с самого начала истории компью-теров). Но такие последователь-ности не удобны для человека. Итак, ncurses - это своего рода оболочка,​ которая облегчает использование управляющих последовательностей. В коде примера я добавил %%//nc%% после вызова функций,​ которые относятся к ncurses. Вот функции,​ которые я использовал:​ 
-  * getmaxyx() возвращает размеры терминала 
-  * clear() отчищает экран 
-  * mvaddch() отображает символ в заданном месте 
-  * refresh() делает принудительный вывод в терминал 
-  * endwin() правильно завершает работу терминала при выходе 
-  * initscr() инициализирует библиотеку ncurses 
- 
-===Функция main=== 
- 
-Функция main делает немного (см. //​Листинг 1//). Она инициализирует экран (строка 6) и каждую секунду обновляет массив снежинок (строка 12). Здесь только одна особенность - функция atexit(). Она используется,​ чтобы инструк-тировать приложение,​ что перед выходом нужно вызвать эту функцию. Её код дан в //​Листинге 2//. Она просто вызывает endwin(). Заметьте,​ что здесь используется приём, называемый "​указатель на функцию"​ (function pointer). Мы можем использовать указатели на фукции так же, как и на данные,​ и это просто имя функции без скобок. 
- 
-<code c> 
-Листинг 1: main() 
-1.int main() 
-2.{ 
-3.    char * field=NULL; 
-4.    int row=0; 
-5.    int col=0; 
-6.    initscr(); //nc 
-7.    atexit(exitfun);​ 
-8. 
-9.    /* Вечный снегопад ! */ 
-10.    while(1) 
-11.    { 
-12.        updateFlakes(&​field,&​row,&​col);​ 
-13.        if(field==NULL) 
-14.        { 
-15.            break; 
-16.        } 
-17.        drawScreen(field,​row,​col);​ 
-18.        sleep(1); 
-19.    } 
-20.    return 0; 
-21.} 
-</​code>​ 
- 
-<code c> 
-Листинг 2: exitfun() 
-1. /* При выходе правильно закрыть терминал */  
-2. void exitfun() ​ 
-3. {  
-4.    endwin(); //nc  
-5.}  
-</​code>​ 
- 
-===Да сделаем,​ что будет снег=== 
- 
-В main() у нас есть хранилище для числа строк, столбцов и массив снежинок. Мы передаём эти три параметра в функцию updateFlakes() (Листинг 3). Если размер терминала изменён,​ она выделяет память. Эта функция считывает размеры терминала при каждом вызове. Если они не соответствуют хранимым в main, то выделяется новый массив,​ и всё начинается с начала. В строках с 6 по 19 считываются размеры и выделяется память (и освобождается занятая,​ если она есть). Здесь как раз и используется динамическое выделение. Иногда до компиля-ции неизвестно,​ сколько понадобится памяти. Здесь нам нужен один байт для каждой позиции на экране,​ но размер окна не фиксирован во время компиляции,​ поэтому необходимо это узнать и выделить нужное количество памяти. То же самое происходит при изменении размеров окна, когда нужно обновить количество необходимой памяти. Для этого исполь-зуются функции malloc() (строка 15) и free() (строка 13). Функции malloc() (что означает memory allocate - выделить память) нужно передать количество байт, которые нужно выделить,​ и она возвращает указатель на это количество байт (или NULL, если память закончилась). Функция free() информирует систе-му,​ что память больше не нужна. Неправильное соче-тание malloc() и free() приве-дёт к утечкам памяти и, в конце концов,​ к краху при-ложения. Ну, и это всё. Просто,​ не так ли? Теперь становится понятно,​ сколько можно создать себе проблем,​ используя динамическое выделение памяти?​ 
- 
-<code c> 
-Листинг 3: updateFlakes 
- 1./* Обновим стурктуру */  
- ​2.void updateFlakes(char ** fieldIn, int *rowIn, int *colIn) ​ 
- ​3.{ ​ 
- ​4. ​   int numnew=0; int row=0; int col=0; int i=0;  
- ​5. ​   char *field=*fieldIn; ​ 
- ​6. ​   getmaxyx(stdscr,​row,​col);​ //nc  
- 7. 
- ​8. ​   /* Создадим новый field */  
- ​9. ​   if(field==NULL || *rowIn!=row || *colIn!=col) ​ 
-10.    {  
-11.        if(field!=NULL) ​ 
-12.        {  
-13.            free(field); ​ 
-14.        }  
-15.        *fieldIn=malloc(row*col); ​ 
-16.        field=*fieldIn; ​ 
-17.        memset(field,​0,​row*col); ​ 
-18.        *rowIn=row; *colIn=col; ​ 
-19.    }  
-20.  
-21.    /* Применим гравитацию ! */  
-22.    memmove(&​field[col],&​field[0],​(row-1)*col); ​ 
-23.    memset(field,​0,​col); ​ 
-24.    numnew=random()%(col/​2); ​ 
-25.    for(i=0;​i<​numnew;​i++) ​ 
-26.    {  
-27.        field[random()%col]=1; ​ 
-28.    }  
-29.}  
-</​code>​ 
- 
-|{{:​fullcircle:​20:​memmove.png|}}|По-настоящему сложная часть этой функции - это управление памятью. Мы используем одномерный массив (char* field) для хранения двумерных дан-ных (две стороны экрана). Проще говоря,​ это означает,​ field[0] соответствует строке 0, столбцу 0; field[1] - сроке 0, столбцу 1; field[row] - сроке 1, столбцу 0, а field[row+1] - строке 1, столбцу 1. Легче работать с одним большим массивом,​ чем с массивом массивов. На Рисунке 1 показана эта схема для экрана размером в 5 строк и 3 столбца. Мы используем memset() (строка 17), что бы заполнить выделенный массив нулями (это всегда хорошая идея, потому что выделенная память обычно занята мусором). Однако магия происхо-дит в строке 22 в функции memmove(), которая передвигает первые row-1 строк на col байт. На Рисунке 1 этот сдвиг изоб-ражен пунктирной стрел-кой. Когда это сделано,​ мы присваиваем нули новой "​первой"​ строке и меняем случайные ячейки на 1 (это будет снег).| 
- 
-===Да будет снег=== 
-Наконец,​ нужно просто перебрать массив и вывес-ти снег на экран. Это по-казано в //​Листинге 4//. Это лишь два for-цикла:​ один - для строк, другой - для столбцов;​ затем принять решение выводить снежинку или нет. 
- 
-===Заключение=== 
-Уже было освещено много "​сложного материала",​ хотя пред-ставлено всего четыре статьи. Можно увидеть,​ что в этой статье мы уже отходим от общего программирования на С и смеща-емся к приложениям более специ-фичным для Linux/​Ubuntu. Целью этих статей является продолжение этого и всё большее обращение к харак-терным для Linux приёмам программирования,​ и на этом я желаю всем вам, энту-зиастам,​ счастливого Нового Года, полного открытий. 
- 
-<code c> 
-Листинг 4: drawScreen() 
- 1./* Да будет снег */  
- ​2.void drawScreen(char * field, int row, int col)  
- ​3.{ ​ 
- ​4. ​   clear(); //nc  
- ​5. ​   int x=0;  
- ​6. ​   int y=0;  
- ​7. ​   for(y=0;​y<​row;​y++) ​ 
- ​8. ​   {  
- ​9. ​       for(x=0;​x<​col;​x++) ​ 
-10.        {  
-11.            if(field[y*col+x]==1) ​ 
-12.            {  
-13.                mvaddch(y,​x,'​*'​);​ //nc  
-14.            }  
-15.        }  
-16.    }  
-17.    refresh(); //nc  
-18.}  
-</​code>​ 
- 
-===Упражнения:​=== 
-  * Заставьте приложение работать на вашей системе (необ-ходимо выяснить нужные заголовки,​ подсказка:​ обратитесь к man-страницам вызовов,​ которые выдают ясные ошибки). 
-  * Вместо передачи exitfun() в atexit() можно сразу передать endwin(); проверьте,​ что это работает. Прочитайте man-страницу. Прототипы каких функ-ций она принимает. Почему нет смысла передавать функции,​ не возвращающие значения. 
-  * Отключите повторное выделение памяти для field. Попробуйте теперь изменить размер окна. Есть ли плюсы? 
-  * Заметьте,​ что используемый массив field не освобождается функцией free() при выходе. Это не создаст проблем,​ не приведёт к утечке памяти,​ и ядро освободит память. Сделайте field глобальной переменной (поместив вне main()) и освободите память при выходе. 
-  * Напишите приложение с кодом while(1){malloc(1);​},​ чтобы подтвердить,​ что память в конце концов закончится. 
-  * Изучите man-страницы random и srand, чтобы узнать,​ как инициировать генератор случайных чисел. 
- 
-{{:​fullcircle:​20:​snow.png|}} 
-<note important>​Elie De Brauwer - фанатик Linux из Бельгии,​ сейчас рабо-тает разработчиком встроен-ного программного обеспече-ния в одной из ведущих компа-ний по спутниковой передаче данных. Когда он не со своей семьёй,​ он любит играть с технологиями и проводит дни ожидая,​ когда Blizzard наконец выпустит Diablo III.</​note>​ 
- 
-[[http://​help.ubuntu.ru/​fullcircle/​fcm-ru-20|К содержанию номера]] 
- 
-[[http://​help.ubuntu.ru/​fullcircle|К архиву журналов]] 
- 
-{{tag>​Си Програмирование Full_Circle}}