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

Различия

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

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

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