HOW-TO: Программа на Си. Часть 4 Сравнение версий

Различия

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

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

Следующая версия
Предыдущая версия
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}}