Различия
Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
fullcircle:20:программа_на_си_ч4 [2010/03/31 19:37] |
fullcircle:20:программа_на_си_ч4 [2010/04/14 00:30] (текущий) |
||
---|---|---|---|
Строка 1: | Строка 1: | ||
- | ====== HOW-TO: Програма на Си. Часть 4 ====== | + | ====== HOW-TO: Программа на Си. Часть 4 ====== |
+ | |||
+ | - [[..:17:программа_на_си_ч1|Программа на Си. Часть 1]] | ||
+ | - [[..:18:программа_на_си_ч2|Программа на Си. Часть 2]] | ||
+ | - [[..:19:программа_на_си_ч3|Программа на Си. Часть 3]] | ||
+ | - [[..:20:программа_на_си_ч4|Программа на Си. Часть 4]] | ||
+ | - [[..:21:программа_на_си_ч5|Программа на Си. Часть 5]] | ||
+ | - [[..:22:программа_на_си_ч6|Программа на Си. Часть 6]] | ||
+ | - [[..:23:программа_на_си_ч7|Программа на Си. Часть 7]] | ||
+ | - [[..:24:программа_на_си_ч8|Программа на Си. Часть 8]] | ||
+ | |||
+ | ------ | ||
<style right> | <style right> | ||
Строка 5: | Строка 16: | ||
</style> | </style> | ||
- | В четвертой статье я расскажу о важной теме, для каждого Си програм-миста, потому что она может причинить множество неприят-ностей: динамическое выделе-ние памяти (dynamic memory allocation). Непонимание этого механизма (и указателей) может привести к утечкам памяти и ошибкам в приложе-нии (например, к ошибке Segmantation Fault). | + | В четвертой статье я расскажу о важной теме, для каждого Си программиста, потому что она может причинить множество неприятностей: динамическое выделение памяти (dynamic memory allocation). Непонимание этого механизма (и указателей) может привести к утечкам памяти и ошибкам в приложении (например, к ошибке Segmantation Fault). |
- | Сейчас пора каникул, и поэтому примером будет приложение, которое создаёт ASCII-снег. Чтобы создать этот эффект, я буду ис-пользовать небольшую часть биб-лиотеки, называемой "ncurses". За дополнительной информацией о библиотеке я советую посетить [[http://tldp.org/HOWTO/NCURSES-Programming-HOWTO|http://tldp.org/HOWTO/NCURSES-Programming-HOWTO]], потому что я буду говорить только о функциях, которые используются в приложении. | + | Сейчас пора каникул, и поэтому примером будет приложение, которое создаёт ASCII-снег. Чтобы создать этот эффект, я буду использовать небольшую часть библиотеки, называемой "ncurses". За дополнительной информацией о библиотеке я советую посетить [[http://tldp.org/HOWTO/NCURSES-Programming-HOWTO|http://tldp.org/HOWTO/NCURSES-Programming-HOWTO]], потому что я буду говорить только о функциях, которые используются в приложении. |
===== Использование ncurses ===== | ===== Использование ncurses ===== | ||
Строка 31: | Строка 42: | ||
Запуск ldd показывает, что приложению требуется наличие в нашей системе библиотеки libncurses.so.5. Ещё это значит, что не удастся запустить этот бинарный файл в системе, где нет этой библиотеки. | Запуск ldd показывает, что приложению требуется наличие в нашей системе библиотеки libncurses.so.5. Ещё это значит, что не удастся запустить этот бинарный файл в системе, где нет этой библиотеки. | ||
- | Что может ncurses? На самом деле, текстовый терминал - это странная вещь. Используя printf(), можно вывести текст, но он всегда появляется в конце строки. Нельзя перематывать назад, использовать цвета, печатать жирные символы и т.д. Также существуют "управляющие последователь-ности" (escape sequences), которые влияют на поведение курсора и вывод текста в таких терминалах (они существуют с самого начала истории компью-теров). Но такие последователь-ности не удобны для человека. Итак, ncurses - это своего рода оболочка, которая облегчает использование управляющих последовательностей. В коде примера я добавил %%//nc%% после вызова функций, которые относятся к ncurses. Вот функции, которые я использовал: | + | Что может ncurses? На самом деле, текстовый терминал - это странная вещь. Используя printf(), можно вывести текст, но он всегда появляется в конце строки. Нельзя перематывать назад, использовать цвета, печатать жирные символы и т.д. Также существуют "управляющие последовательности" (escape sequences), которые влияют на поведение курсора и вывод текста в таких терминалах (они существуют с самого начала истории компьютеров). Но такие последовательности не удобны для человека. Итак, ncurses - это своего рода оболочка, которая облегчает использование управляющих последовательностей. В коде примера я добавил %%//nc%% после вызова функций, которые относятся к ncurses. Вот функции, которые я использовал: |
* getmaxyx() возвращает размеры терминала | * getmaxyx() возвращает размеры терминала | ||
Строка 42: | Строка 53: | ||
===== Функция main ===== | ===== Функция main ===== | ||
- | Функция main делает немного (см. //Листинг 1//). Она инициализирует экран (строка 6) и каждую секунду обновляет массив снежинок (строка 12). Здесь только одна особенность - функция atexit(). Она используется, чтобы инструк-тировать приложение, что перед выходом нужно вызвать эту функцию. Её код дан в //Листинге 2//. Она просто вызывает endwin(). Заметьте, что здесь используется приём, называемый "указатель на функцию" (function pointer). Мы можем использовать указатели на фукции так же, как и на данные, и это просто имя функции без скобок. | + | Функция main делает немного (см. //Листинг 1//). Она инициализирует экран (строка 6) и каждую секунду обновляет массив снежинок (строка 12). Здесь только одна особенность - функция atexit(). Она используется, чтобы инструктировать приложение, что перед выходом нужно вызвать эту функцию. Её код дан в //Листинге 2//. Она просто вызывает endwin(). Заметьте, что здесь используется приём, называемый "указатель на функцию" (function pointer). Мы можем использовать указатели на функции так же, как и на данные, и это просто имя функции без скобок. |
//**Листинг 1: main()**// | //**Листинг 1: main()**// | ||
Строка 80: | Строка 91: | ||
===== Да сделаем, что будет снег ===== | ===== Да сделаем, что будет снег ===== | ||
- | В main() у нас есть хранилище для числа строк, столбцов и массив снежинок. Мы передаём эти три параметра в функцию updateFlakes() (Листинг 3). Если размер терминала изменён, она выделяет память. Эта функция считывает размеры терминала при каждом вызове. Если они не соответствуют хранимым в main, то выделяется новый массив, и всё начинается с начала. В строках с 6 по 19 считываются размеры и выделяется память (и освобождается занятая, если она есть). Здесь как раз и используется динамическое выделение. Иногда до компиля-ции неизвестно, сколько понадобится памяти. Здесь нам нужен один байт для каждой позиции на экране, но размер окна не фиксирован во время компиляции, поэтому необходимо это узнать и выделить нужное количество памяти. То же самое происходит при изменении размеров окна, когда нужно обновить количество необходимой памяти. Для этого исполь-зуются функции malloc() (строка 15) и free() (строка 13). Функции malloc() (что означает memory allocate - выделить память) нужно передать количество байт, которые нужно выделить, и она возвращает указатель на это количество байт (или NULL, если память закончилась). Функция free() информирует систе-му, что память больше не нужна. Неправильное соче-тание malloc() и free() приве-дёт к утечкам памяти и, в конце концов, к краху при-ложения. Ну, и это всё. Просто, не так ли? Теперь становится понятно, сколько можно создать себе проблем, используя динамическое выделение памяти? | + | В main() у нас есть хранилище для числа строк, столбцов и массив снежинок. Мы передаём эти три параметра в функцию updateFlakes() (Листинг 3). Если размер терминала изменён, она выделяет память. Эта функция считывает размеры терминала при каждом вызове. Если они не соответствуют хранимым в main, то выделяется новый массив, и всё начинается с начала. В строках с 6 по 19 считываются размеры и выделяется память (и освобождается занятая, если она есть). Здесь как раз и используется динамическое выделение. Иногда до компиляции неизвестно, сколько понадобится памяти. Здесь нам нужен один байт для каждой позиции на экране, но размер окна не фиксирован во время компиляции, поэтому необходимо это узнать и выделить нужное количество памяти. То же самое происходит при изменении размеров окна, когда нужно обновить количество необходимой памяти. Для этого используются функции malloc() (строка 15) и free() (строка 13). Функции malloc() (что означает memory allocate - выделить память) нужно передать количество байт, которые нужно выделить, и она возвращает указатель на это количество байт (или NULL, если память закончилась). Функция free() информирует систему, что память больше не нужна. Неправильное сочетание malloc() и free() приведёт к утечкам памяти и, в конце концов, к краху приложения. Ну, и это всё. Просто, не так ли? Теперь становится понятно, сколько можно создать себе проблем, используя динамическое выделение памяти? |
//**Листинг 3: updateFlakes**// | //**Листинг 3: updateFlakes**// | ||
Строка 116: | Строка 127: | ||
{{:fullcircle:20:memmove.png }} | {{: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 (это будет снег). | + | По-настоящему сложная часть этой функции - это управление памятью. Мы используем одномерный массив (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-цикла: один - для строк, другой - для столбцов; затем принять решение выводить снежинку или нет. | + | Наконец, нужно просто перебрать массив и вывести снег на экран. Это показано в //Листинге 4//. Это лишь два for-цикла: один - для строк, другой - для столбцов; затем принять решение выводить снежинку или нет. |
===== Заключение ===== | ===== Заключение ===== | ||
- | Уже было освещено много "сложного материала", хотя пред-ставлено всего четыре статьи. Можно увидеть, что в этой статье мы уже отходим от общего программирования на С и смеща-емся к приложениям более специ-фичным для Linux/Ubuntu. Целью этих статей является продолжение этого и всё большее обращение к харак-терным для Linux приёмам программирования, и на этом я желаю всем вам, энту-зиастам, счастливого Нового Года, полного открытий. | + | Уже было освещено много "сложного материала", хотя представлено всего четыре статьи. Можно увидеть, что в этой статье мы уже отходим от общего программирования на С и смещаемся к приложениям более специфичным для Linux/Ubuntu. Целью этих статей является продолжение этого и всё большее обращение к характерным для Linux приёмам программирования, и на этом я желаю всем вам, энтузиастам, счастливого Нового Года, полного открытий. |
//**Листинг 4: drawScreen()**// | //**Листинг 4: drawScreen()**// | ||
Строка 147: | Строка 158: | ||
18.} | 18.} | ||
</code> | </code> | ||
- | |||
===== Упражнения ===== | ===== Упражнения ===== | ||
* Заставьте приложение работать на вашей системе (необходимо выяснить нужные заголовки, подсказка: обратитесь к man-страницам вызовов, которые выдают ясные ошибки). | * Заставьте приложение работать на вашей системе (необходимо выяснить нужные заголовки, подсказка: обратитесь к man-страницам вызовов, которые выдают ясные ошибки). | ||
- | * Вместо передачи exitfun() в atexit() можно сразу передать endwin(); проверьте, что это работает. Прочитайте man-страницу. Прототипы каких функ-ций она принимает. Почему нет смысла передавать функции, не возвращающие значения. | + | * Вместо передачи exitfun() в atexit() можно сразу передать endwin(); проверьте, что это работает. Прочитайте man-страницу. Прототипы каких функций она принимает. Почему нет смысла передавать функции, не возвращающие значения. |
* Отключите повторное выделение памяти для field. Попробуйте теперь изменить размер окна. Есть ли плюсы? | * Отключите повторное выделение памяти для field. Попробуйте теперь изменить размер окна. Есть ли плюсы? | ||
* Заметьте, что используемый массив field не освобождается функцией free() при выходе. Это не создаст проблем, не приведёт к утечке памяти, и ядро освободит память. Сделайте field глобальной переменной (поместив вне main()) и освободите память при выходе. | * Заметьте, что используемый массив field не освобождается функцией free() при выходе. Это не создаст проблем, не приведёт к утечке памяти, и ядро освободит память. Сделайте field глобальной переменной (поместив вне main()) и освободите память при выходе. | ||
Строка 163: | Строка 173: | ||
---- | ---- | ||
<style center> | <style center> | ||
+ | - [[..:17:программа_на_си_ч1|Программа на Си. Часть 1]] | ||
+ | - [[..:18:программа_на_си_ч2|Программа на Си. Часть 2]] | ||
+ | - [[..:19:программа_на_си_ч3|Программа на Си. Часть 3]] | ||
+ | - [[..:20:программа_на_си_ч4|Программа на Си. Часть 4]] | ||
+ | - [[..:21:программа_на_си_ч5|Программа на Си. Часть 5]] | ||
+ | - [[..:22:программа_на_си_ч6|Программа на Си. Часть 6]] | ||
+ | - [[..:23:программа_на_си_ч7|Программа на Си. Часть 7]] | ||
+ | - [[..:24:программа_на_си_ч8|Программа на Си. Часть 8]] | ||
+ | |||
+ | ---- | ||
+ | |||
//[[..:20|К содержанию номера]]// | //[[..:20|К содержанию номера]]// | ||
Строка 168: | Строка 189: | ||
</style> | </style> | ||
- | {{tag>Си Програмирование Full_Circle}} | + | {{tag>howto Си Программирование Full_Circle}} |