Различия
Здесь показаны различия между двумя версиями данной страницы.
Следующая версия | Предыдущая версия | ||
fullcircle:20:программа_на_си_ч4 [2010/03/30 23:40] создано |
fullcircle:20:программа_на_си_ч4 [2010/04/14 00:30] (текущий) |
||
---|---|---|---|
Строка 1: | Строка 1: | ||
- | ======HOW-TO====== | + | ====== HOW-TO: Программа на Си. Часть 4 ====== |
- | =====Програма на Си. Часть 4===== | + | |
- | ===Автор: Эли Дэ Брувэр (Elie De Brauwer)=== | + | |
- | В четвертой статье я расскажу о важной теме, для каждого Си програм-миста, потому что она может причинить множество неприят-ностей: динамическое выделе-ние памяти (dynamic memory allocation). Непонимание этого механизма (и указателей) может привести к утечкам памяти и ошибкам в приложе-нии (например, к ошибке Segmantation Fault). | + | - [[..:17:программа_на_си_ч1|Программа на Си. Часть 1]] |
+ | - [[..:18:программа_на_си_ч2|Программа на Си. Часть 2]] | ||
+ | - [[..:19:программа_на_си_ч3|Программа на Си. Часть 3]] | ||
+ | - [[..:20:программа_на_си_ч4|Программа на Си. Часть 4]] | ||
+ | - [[..:21:программа_на_си_ч5|Программа на Си. Часть 5]] | ||
+ | - [[..:22:программа_на_си_ч6|Программа на Си. Часть 6]] | ||
+ | - [[..:23:программа_на_си_ч7|Программа на Си. Часть 7]] | ||
+ | - [[..:24:программа_на_си_ч8|Программа на Си. Часть 8]] | ||
- | Сейчас пора каникул, и поэтому примером будет приложение, которое создаёт ASCII-снег. Чтобы создать этот эффект, я буду ис-пользовать небольшую часть биб-лиотеки, называемой "ncurses". За дополнительной информацией о библиотеке я советую посетить [[http://tldp.org/HOWTO/NCURSES-Programming-HOWTO|http://tldp.org/HOWTO/NCURSES-Programming-HOWTO]], потому что я буду говорить только о функциях, которые используются в приложении. | + | ------ |
+ | |||
+ | <style right> | ||
+ | //Автор: Эли Дэ Брувэр (Elie De Brauwer)// | ||
+ | </style> | ||
+ | |||
+ | В четвертой статье я расскажу о важной теме, для каждого Си программиста, потому что она может причинить множество неприятностей: динамическое выделение памяти (dynamic memory allocation). Непонимание этого механизма (и указателей) может привести к утечкам памяти и ошибкам в приложении (например, к ошибке Segmantation Fault). | ||
+ | |||
+ | Сейчас пора каникул, и поэтому примером будет приложение, которое создаёт ASCII-снег. Чтобы создать этот эффект, я буду использовать небольшую часть библиотеки, называемой "ncurses". За дополнительной информацией о библиотеке я советую посетить [[http://tldp.org/HOWTO/NCURSES-Programming-HOWTO|http://tldp.org/HOWTO/NCURSES-Programming-HOWTO]], потому что я буду говорить только о функциях, которые используются в приложении. | ||
+ | |||
+ | ===== Использование ncurses ===== | ||
- | ===Использование ncurses=== | ||
Для использования библиотеки сначала необходимо установить пакеты ncurses: | Для использования библиотеки сначала необходимо установить пакеты ncurses: | ||
- | <code>apt-get install libncurses5 libncurses5-dev</code> | + | |
+ | apt-get install libncurses5 libncurses5-dev | ||
Затем в начало исходного файла нужно добавить заголовок ncurses, дописав **#include <ncurses.h>**. Новым является то, что ncurses предоставляется в виде динамической библиотеки, что означает две вещи: во-первых, компоновщик должен связать наш исходный код с библиотекой ncurses. Это делается такой командой: | Затем в начало исходного файла нужно добавить заголовок ncurses, дописав **#include <ncurses.h>**. Новым является то, что ncurses предоставляется в виде динамической библиотеки, что означает две вещи: во-первых, компоновщик должен связать наш исходный код с библиотекой ncurses. Это делается такой командой: | ||
- | <code>gcc -Wall -lncurses snow.c -o snow</code> | + | |
+ | gcc -Wall -lncurses snow.c -o snow | ||
Параметр **-l** инструктирует компоновщик добавить разделяемую библиотеку ncurses. И в результате этого мы увидим такой вывод: | Параметр **-l** инструктирует компоновщик добавить разделяемую библиотеку ncurses. И в результате этого мы увидим такой вывод: | ||
- | <code>edb@lapedb:~/fullcircle/c-4$ ldd snow* | + | |
+ | <file> | ||
+ | edb@lapedb:~/fullcircle/c-4$ ldd snow* | ||
linux-gate.so.1 => (0xb805c000)* | linux-gate.so.1 => (0xb805c000)* | ||
libncurses.so.5 => /lib/libncurses.so.5 (0xb7ff7000)* | libncurses.so.5 => /lib/libncurses.so.5 (0xb7ff7000)* | ||
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e99000)* | libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e99000)* | ||
libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7e94000)* | libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7e94000)* | ||
- | /lib/ld-linux.so.2 (0xb8042000)</code> | + | /lib/ld-linux.so.2 (0xb8042000) |
+ | </file> | ||
Запуск ldd показывает, что приложению требуется наличие в нашей системе библиотеки libncurses.so.5. Ещё это значит, что не удастся запустить этот бинарный файл в системе, где нет этой библиотеки. | Запуск ldd показывает, что приложению требуется наличие в нашей системе библиотеки libncurses.so.5. Ещё это значит, что не удастся запустить этот бинарный файл в системе, где нет этой библиотеки. | ||
- | Что может ncurses? На самом деле, текстовый терминал - это странная вещь. Используя printf(), можно вывести текст, но он всегда появляется в конце строки. Нельзя перематывать назад, использовать цвета, печатать жирные символы и т.д. Также существуют "управляющие последователь-ности" (escape sequences), которые влияют на поведение курсора и вывод текста в таких терминалах (они существуют с самого начала истории компью-теров). Но такие последователь-ности не удобны для человека. Итак, ncurses - это своего рода оболочка, которая облегчает использование управляющих последовательностей. В коде примера я добавил %%//nc%% после вызова функций, которые относятся к ncurses. Вот функции, которые я использовал: | + | Что может ncurses? На самом деле, текстовый терминал - это странная вещь. Используя printf(), можно вывести текст, но он всегда появляется в конце строки. Нельзя перематывать назад, использовать цвета, печатать жирные символы и т.д. Также существуют "управляющие последовательности" (escape sequences), которые влияют на поведение курсора и вывод текста в таких терминалах (они существуют с самого начала истории компьютеров). Но такие последовательности не удобны для человека. Итак, ncurses - это своего рода оболочка, которая облегчает использование управляющих последовательностей. В коде примера я добавил %%//nc%% после вызова функций, которые относятся к ncurses. Вот функции, которые я использовал: |
* getmaxyx() возвращает размеры терминала | * getmaxyx() возвращает размеры терминала | ||
* clear() отчищает экран | * clear() отчищает экран | ||
Строка 29: | Строка 51: | ||
* initscr() инициализирует библиотеку ncurses | * initscr() инициализирует библиотеку ncurses | ||
- | ===Функция main=== | + | ===== Функция main ===== |
- | Функция main делает немного (см. //Листинг 1//). Она инициализирует экран (строка 6) и каждую секунду обновляет массив снежинок (строка 12). Здесь только одна особенность - функция atexit(). Она используется, чтобы инструк-тировать приложение, что перед выходом нужно вызвать эту функцию. Её код дан в //Листинге 2//. Она просто вызывает endwin(). Заметьте, что здесь используется приём, называемый "указатель на функцию" (function pointer). Мы можем использовать указатели на фукции так же, как и на данные, и это просто имя функции без скобок. | + | Функция main делает немного (см. //Листинг 1//). Она инициализирует экран (строка 6) и каждую секунду обновляет массив снежинок (строка 12). Здесь только одна особенность - функция atexit(). Она используется, чтобы инструктировать приложение, что перед выходом нужно вызвать эту функцию. Её код дан в //Листинге 2//. Она просто вызывает endwin(). Заметьте, что здесь используется приём, называемый "указатель на функцию" (function pointer). Мы можем использовать указатели на функции так же, как и на данные, и это просто имя функции без скобок. |
//**Листинг 1: main()**// | //**Листинг 1: main()**// | ||
Строка 67: | Строка 89: | ||
</code> | </code> | ||
- | ===Да сделаем, что будет снег=== | + | ===== Да сделаем, что будет снег ===== |
- | В 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**// | ||
Строка 104: | Строка 126: | ||
</code> | </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 (это будет снег).| | + | {{: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-цикла: один - для строк, другой - для столбцов; затем принять решение выводить снежинку или нет. | + | |
- | ===Заключение=== | + | Наконец, нужно просто перебрать массив и вывести снег на экран. Это показано в //Листинге 4//. Это лишь два for-цикла: один - для строк, другой - для столбцов; затем принять решение выводить снежинку или нет. |
- | Уже было освещено много "сложного материала", хотя пред-ставлено всего четыре статьи. Можно увидеть, что в этой статье мы уже отходим от общего программирования на С и смеща-емся к приложениям более специ-фичным для Linux/Ubuntu. Целью этих статей является продолжение этого и всё большее обращение к харак-терным для Linux приёмам программирования, и на этом я желаю всем вам, энту-зиастам, счастливого Нового Года, полного открытий. | + | |
+ | ===== Заключение ===== | ||
+ | |||
+ | Уже было освещено много "сложного материала", хотя представлено всего четыре статьи. Можно увидеть, что в этой статье мы уже отходим от общего программирования на С и смещаемся к приложениям более специфичным для Linux/Ubuntu. Целью этих статей является продолжение этого и всё большее обращение к характерным для Linux приёмам программирования, и на этом я желаю всем вам, энтузиастам, счастливого Нового Года, полного открытий. | ||
//**Листинг 4: drawScreen()**// | //**Листинг 4: drawScreen()**// | ||
Строка 133: | Строка 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()) и освободите память при выходе. | ||
Строка 143: | Строка 168: | ||
{{ :fullcircle:20:snow.png?500 }} | {{ :fullcircle:20:snow.png?500 }} | ||
- | <note important>Elie De Brauwer - фанатик Linux из Бельгии, сейчас работает разработчиком встроенного программного обеспечения в одной из ведущих компаний по спутниковой передаче данных. Когда он не со своей семьёй, он любит играть с технологиями и проводит дни ожидая, когда Blizzard наконец выпустит Diablo III.</note> | ||
- | [[:fullcircle/20|← К содержанию номера]] | + | <note>Elie De Brauwer - фанатик Linux из Бельгии, сейчас работает разработчиком встроенного программного обеспечения в одной из ведущих компаний по спутниковой передаче данных. Когда он не со своей семьёй, он любит играть с технологиями и проводит дни ожидая, когда Blizzard наконец выпустит Diablo III.</note> |
+ | |||
+ | ---- | ||
+ | <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|К содержанию номера]]// | ||
- | [[:fullcircle|← К архиву журналов]] | + | //[[:fullcircle|К архиву журналов]]// |
+ | </style> | ||
- | {{tag>Си Програмирование Full_Circle}} | + | {{tag>howto Си Программирование Full_Circle}} |