Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 3 (всего у книги 55 страниц)
В течение многих лет определение С де-факто можно было найти в первом издании книги Брайана Кернигана и Денниса Ричи «Язык программирования С» (Brian Kernighan & Dennis Ritchie, The С Programming Language). Эта книга описала С, как он существовал для Unix и на системах, на которые его перенесли разработчики лаборатории Bell Labs. На протяжении данной книги мы называем его как «оригинальный С», хотя обычным является также название «С Кернигана и Ричи» («K&R С»), по именам двух авторов книги. (Деннис Ричи разработал и реализовал С.)
Стандарт ISO С 1990 г.[17]17
Между народный стандарт ISO/IEC 9899-1990 описывает разновидность языка С известную также как C89 или C90 – Примеч. науч. ред.
[Закрыть] формализовал определения языка, включая функции библиотеки С (такие, как printf()
и fopen()
). Комитет по стандартам С проделал замечательную работу по стандартизации существующей практики и избежал введения новых возможностей, с одним значительным исключением (и несколькими незначительными). Наиболее заметным изменением языка было использование прототипов функций, заимствованных от С++.
Стандартные языки программирования С, C++ и Java используют прототипы функций для объявлений и определений функций. Прототип описывает не только возвращаемое значение функции, но также и число и тип ее аргументов. С прототипами компилятор может выполнить проверку типов в точке вызова функции:
Объявление
extern int myfunc(struct my_struct *a,
struct my_struct *b, double c, int d);
Определение
int myfunc(struct my_struct *a,
struct my_struct *b, double c, int d) {
...
}
...
struct my_struct s, t;
int j;
...
/* Вызов функции, где-то в другом месте: */
j = my_func(&s, &t, 3.1415, 42);
Это правильный вызов функции. Но рассмотрите ошибочный вызов:
j = my_func(-1, -2, 0);
/* Ошибочные число и типы аргументов */
Компилятор может сразу же определить этот вызов как неверный. Однако, в оригинальном С функции объявляются без указания списка аргументов:
extern int myfunc();
/* Возвращает int, аргументы неизвестны */
Более того, определения функций перечисляют имена параметров в заголовке функции, затем объявляют параметры перед телом функции. Параметры типа int объявлять не нужно, и если функция возвращает int, его тоже не нужно объявлять:
myfunc(a, b, с, d); /* Возвращаемый тип int*/
struct my_struct *а, *b;
double с;
/* Обратите внимание, нет объявления параметра d*/
{
...
}
Рассмотрите снова тот же ошибочный вызов функции: 'j = my_func(-1, -2 , 0);
'. В оригинальном С у компилятора нет возможности узнать, что вы (ошибочно, полагаем) передали my_func()
ошибочные аргументы. Подобные ошибочные вызовы обычно приводят к трудно устранимым проблемам времени исполнения (таким, как ошибки сегментации, из-за чего программа завершается), и для работы с такими вещами была создана программа Unix lint
.
Поэтому, хотя прототипы функции и были радикальным отходом от существующей практики, дополнительную проверку типов посчитали слишком важной, чтобы обходиться без нее, и после небольшого сопротивления она была добавлена в язык.
Для С стандарта 1990 г. код, написанный в оригинальном стиле, является действительным как для объявлений, так и для определений. Это дает возможность продолжать компилировать миллионы строк существующего кода с помощью компилятора, удовлетворяющего стандарту. Новый код, очевидно, должен быть написан с прототипами из-за улучшенных возможностей проверки ошибок времени компилирования.
Стандарт С 1999 г.[18]18
Стандарт C99 (ISO/IЕС 9899-1999) – Примеч. науч. ред.
[Закрыть] продолжает допускать объявления и определения в оригинальном стиле. Однако, правило «неявного int
» было убрано; функции должны иметь возвращаемый тип, а все параметры должны быть объявлены.
Более того, когда программа вызывала функцию, которая не была формально объявлена, оригинальный С создал бы для функции неявное объявление с возвращаемым типом int
. С стандарта 1999 г. делал то же самое, дополнительно отметив, что у него не было информации о параметрах. С стандарта 1999 г. не предоставляет больше возможности «автоматического объявления».
Другими заметными дополнениями в стандарте С являются ключевое слово const
, также из С++, и ключевое слово volatile
, которое придумал комитет. Для кода, который вы увидите в этой книге, наиболее важной вещью является понимание различных синтаксисов объявлений и определений функций.
Для кода V7, использующего определения в оригинальном стиле, мы добавили комментарии, показывающие эквивалентный прототип. В остальных случаях мы оставили код как есть, предпочитая показать его точно таким, каким он был первоначально написан, и как бы вы его увидели, если бы сами загрузили код.
Хотя стандарт С 1999 г. добавляет некоторые дополнительные ключевые слова и возможности, отсутствующие в версии 1990 г., мы решили придерживаться диалекта 1990 г, поскольку компиляторы C99 не являются пока типичными. Практически, это не имеет значения: код C89 должен компилироваться и запускаться без изменений при использовании компилятора C99, а новые возможности C99 не затрагивают наше обсуждение или использование фундаментальных API Linux/Unix.
1.4. Почему программы GNU лучшеЧто делает программу GNU программой GNU?[19]19
Этот раздел адаптирован из статьи автора, который издавался в выпуске 16 Linux Journal (См. http://www.linuxjournal.com/article.php?sid=1135
) Перепечатано и адаптировано по разрешению – Примеч. автора.
[Закрыть] Что делает программное обеспечение GNU «лучше» по сравнению с другим (платным или бесплатным) программным обеспечением? Наиболее очевидной разницей является общедоступная лицензия (General Public License – GPL), которая описывает условия распространения для программного обеспечения GNU. Но это обычно не причина, чтобы вы могли услышать, как люди говорят: «Дайте GNU-версию xyz
, она намного лучше». Программное обеспечение GNU в общем более устойчиво, имеет лучшую производительность, чем в стандартных версиях Unix. В данном разделе мы рассмотрим некоторые причины этого явления, а также рассмотрим документ, описывающий принципы проектирования программного обеспечения GNU.
«Стандарты кодирования GNU» (GNU Coding Standards) описывают создание программного обеспечения для проекта GNU. Они охватывает ряд тем. Вы можете найти GNU Coding Standards по адресу http://www.gnu.org/prep/standards.html
. Смотрите в онлайн-версии ссылки на исходные файлы в других форматах.
В данном разделе мы описываем лишь те части GNU Coding Standards, которые относятся к проектированию и реализации программ.
Глава 3 GNU Coding Standards содержит общие советы относительно проектирования программ. Четырьмя главными проблемами являются совместимость (со стандартами и с Unix), язык, использование нестандартных возможностей других программ (одним словом, «ничего»), и смысл «переносимости».
Важной целью является совместимость со стандартом С и POSIX, а также, в меньшей степени, с Berkley Unix. Но она не преобладает. Общей идеей является предоставление всех необходимых возможностей через аргументы командной строки для предоставления точного режима ISO или POSIX.
Предпочтительным языком для написания программного обеспечения GNU является С, поскольку это наиболее доступный язык. В мире Unix стандарт С теперь обычен, но если для вас не представляет труда поддержка оригинального С, вы должны сделать это. Хотя стандарты кодирования отдают предпочтение С перед С++, C++ теперь тоже вполне обычен. Примером широко используемого пакета GNU, написанного на С++, является groff
(GNU troff
). Наш опыт говорит, что с GCC, поддерживающим С++, установка groff
не представляет сложности.
Стандарты утверждают, что переносимость является чем-то вроде отвлекающего маневра. Утилиты GNU ясно нацелены на работу с ядром GNU и с библиотекой GNU С[20]20
Это утверждение относится к ядру HURD, которое все еще находится в стадии разработки (в начале 2004 г.) Разработка на основе GCC и библиотеки GNU С (GLIBC) сегодня имеет место большей частью на Linux-системах – Примеч. автора.
[Закрыть]. Но поскольку ядро еще не завершено, и пользователи используют инструменты GNU на не-GNU системах, переносимость желательна, но не является первостепенной задачей. Стандарт рекомендует для достижения переносимости между различными системами Unix использовать Autoconf.
Глава 4 GNU Coding Standards предоставляет общие советы относительно поведения программы. Ниже мы вернемся к одному из ее разделов для более подробного рассмотрения. Глава фокусируется на строении программы, форматировании сообщений об ошибках, написании библиотек (делая их рентабельными) и стандартах для интерфейса командной строки.
Форматирование сообщений об ошибках важно, поскольку несколько инструментов, особенно Emacs, используют сообщения об ошибках, чтобы помочь вам попасть в то место в исходном файле или файле данных, где произошла ошибка.
Утилиты GNU должны использовать для обработки командной строки функцию getopt_long()
. Эта функция предусматривает разбор аргументов командной строки как для опций в стиле традиционного Unix ('gawk -F:...
'), так и для длинных опций в стиле GNU ('gawk –field-separator=:...
'). Все программы должны предусматривать опции –help
и –version
, а когда в одной программе используется длинное имя, оно таким же образом должно использоваться и в другой программе GNU. Для этой цели есть довольно полный список длинных опций, используемых современными GNU-программами.
В качестве простого, но очевидного примера, –verbose
пишется точно таким же способом во всех GNU-программах. Сравните это с -v
, -V
, -d
и т.д. во многих других программах Unix. Большая часть главы 2, «Аргументы, опции и окружение», с. 23, посвящена механике разбора аргументов и опций.
Наиболее привлекательной частью GNU Coding Standards является глава 5, которая описывает написание кода на С, освещая такие темы, как форматирование кода, правильное использование комментариев, чистое использование С, именование ваших функций и переменных, а также объявление или не объявление стандартных системных функций, которые вы хотите использовать.
Форматирование кода является религиозной проблемой; у многих людей разные стили, которые они предпочитают. Лично нам не нравится стиль FSF, и если вы взглянете на gawk
, который мы поддерживаем, вы увидите, что он форматирован в стандартном стиле K&R (стиль расположения кода, использованный в обоих изданиях книги Кернигана и Ричи). Но это единственное отклонение в gawk
от этой части стандартов кодирования.
Тем не менее, хотя нам и не нравится стиль FSF[21]21
Стиль расположения кода, рекомендуемый фондом свободного программного обеспечения (Free Software Foundation) – Примеч. науч. ред.
[Закрыть], мы чувствуем, что при модификации некоторых других программ, придерживание уже использованного стиля кода является исключительно важным. Последовательность в стиле кода более важна, чем сам стиль, который вы выбираете. GNU Coding Standards дает такой же совет. (Иногда невозможно обнаружить последовательный стиль кода, в этом случае программа, возможно, испорчена использованием indent
от GNU или cb
от Unix.)
Что мы сочли важным в главе о написании кода на С, это то, что эти советы хороши для любого кода на С, а не только когда вы работаете над программой GNU. Поэтому, если вы просто учите С или даже если вы уже работали некоторое время на С (или С++), мы рекомендуем вам эту главу, поскольку она заключает в себе многолетний опыт.
Теперь мы рассмотрим раздел, озаглавленный «Написание надежных программ», в главе 4 «Поведение программ для всех программ». Этот раздел описывает принципы проектирования программного обеспечения, которые делают программы GNU лучше их двойников в Unix Мы процитируем выбранные части главы, с несколькими примерами случаев, в которых эти принципы окупились.
Избегайте произвольных ограничений длины или числа любой структуры данных, включая имена файлов, строки, файлы и символы, выделяя все структуры данных динамически. В большинстве инструментов Unix «длинные строки молча срезаются». Это неприемлемо в инструменте GNU.
Это правило, возможно, единственное наиболее важное в проектировании программного обеспечения GNU – никаких произвольных ограничений. Все инструменты GNU должны быть способны обрабатывать произвольные объемы данных.
Хотя это требование, возможно, усложняет работу программиста, оно облегчает жизнь пользователю. С одной стороны, у нас есть пользователь gawk
, регулярно запускающий программу awk
для более чем 650 000 файлов (нет, это не опечатка) для сбора статистики, gawk
заняла бы более 192 мегабайтов пространства данных, и программа работала бы в течение 7 часов. Он не смог бы запустить эту программу, используя другую реализацию awk
.[22]22
Эта ситуация имела место примерно в 1993 г; трюизм даже более очевиден сегодня, когда пользователи обрабатывают с помощью gawk
гигабайты протокольных файлов – Примеч. автора.
[Закрыть]
Утилиты, читающие файлы, не должны удалять символы NUL или любые другие неотображаемые символы, включая символы с кодами больше 0177. Единственными здравыми исключениями были бы утилиты, специально предназначенные для связывания с определенными типами терминалов или принтеров, которые не могут обработать эти символы.
Также хорошо известно, что Emacs может редактировать любые произвольные файлы, включая файлы, содержащие двоичные данные!
По возможности, программы должны обрабатывать должным образом последовательности байтов, представляющих многобайтные символы, используя такие кодировки, как UTF-8 и другие.[23]23
Раздел 13.4, «Не могли бы вы произнести это для меня по буквам?», с. 521, дает обзор многобайтных символов и кодировок – Примеч. автора.
[Закрыть] Каждый системный вызов проверяйте на предмет возвращенной ошибки, если вы не хотите игнорировать ошибки. Включите текст системной ошибки (отperror
или эквивалентной функции) в каждое сообщение об ошибке, возникшей при неудачном системном вызове, также, как и имя файла, если он есть, и имя утилиты. Простого «невозможно открыть foo.с» или «ошибка запуска» недостаточно.
Проверка каждого системного вызова создает устойчивость. Это еще один случай, когда жизнь программиста труднее, а пользователя легче. Подробно описанное сообщение об ошибке значительно упрощает нахождение и разрешение проблем[24]24
Механика проверки ошибок и сообщений о них обсуждаются в разделе 4.3, «Обнаружение неправильной работы» – Примеч. автора.
[Закрыть].
Наконец, мы цитируем главу 1 GNU Coding Standards, которая обсуждает, как написать вашу программу способом, отличным от того, каким написаны программы Unix.
Например, утилиты Unix обычно оптимизированы для минимизации использования памяти, если вы взамен хотите получить скорость, ваша программа будет сильно отличаться. Вы можете хранить весь входной файл в ядре и сканировать его там. вместо использования stdio. Используйте недавно открытый более изящный алгоритм вместо алгоритма Unix-программы. Исключите использование временных файлов. Делайте это в один проход вместо двух (мы сделали это на ассемблере) Или, напротив, сделайте упор на простоте вместо скорости. Для некоторых приложений скорость сегодняшних компьютеров делает адекватными более простые алгоритмы.
Или выберите обобщение. Например, программы Unix часто содержат статичные таблицы или строки фиксированного размера, которые создают произвольные ограничения, используйте вместо этого динамическое выделение памяти. Убедитесь, что ваша программа обрабатывает во входных файлах символы NUL и другие курьезные символы. Добавьте язык программирования для расширяемости и напишите часть программы на этом языке.
Или выделите части программы в независимо используемые библиотеки. Или используйте простой сборщик мусора вместо точного отслеживания, когда освобождать память, или используйте новую возможность GNU, такую как obstacks.
Великолепным примером того, какое отличие можно сделать в алгоритме, является GNU diff
. Одним из первых ранних воплощений нашей системы было AT&T 3B1, система с процессором МС68010, огромными двумя мегабайтами памяти и 80 мегабайтами на диске. Мы проделали (и делаем) кучу исправлений в руководстве для gawk
, файле длиной почти 28 000 строк (хотя в то время он был лишь в диапазоне 10 000 строк). Обычно мы частенько использовали 'diff -с
', чтобы посмотреть на сделанные нами изменения. На этой медленной системе переключение на GNU diff
показало ошеломительную разницу во времени появления контекста diff
. Разница почти всецело благодаря лучшему алгоритму, который использует GNU diff
.
В последнем параграфе упоминается идея структурирования программы как независимо используемой библиотеки, с оболочкой командной строки или другим окружающим се интерфейсом. Примером этого является GDB, отладчик GNU, который реализован в виде инструмента с интерфейсом командной строки поверх отладочной библиотеки. (Разделение основных возможностей GDB от интерфейса командной строки является продолжающимся проектом). Эта реализация дает возможность создать поверх отладочных функциональных возможностей графический интерфейс отладчика.
GNU Coding Standards является стоящим для прочтения документом, если вы хотите разрабатывать новое программное обеспечение GNU, обмениваться существующими программами GNU или просто научиться программировать лучше. Принципы и методики, которые она поддерживает – вот что делает программное обеспечение GNU предпочитаемым выбором в сообществе Unix.
1.5. Пересмотренная переносимостьПереносимость является чем-то вроде Святого Грааля; всегда недостающим впоследствии, но не всегда достижимым и определенно нелегким. Есть несколько аспектов написания переносимого кода. GNU Coding Standards обсуждает многие из них. Но есть и другие стороны. При разработке принимайте переносимость во внимание как на высоком, так и на низком уровнях. Мы рекомендуем следующие правила:
Соответствуйте стандартам
Хотя это может потребовать напряжения, знакомство с формальными стандартами языка, который вы используете, окупается. В частности, обратите внимание на стандарты ISO 1990 и 1999 гг. для С и стандарт 2003 г. для С++, поскольку большинство программ Linux создано на одном из этих двух языков.
В промышленности также широко поддерживается стандарт POSIX для интерфейса библиотечных и системных вызовов, хотя он и большой. Написание в соответствии с POSIX значительно повышает шансы успешного переноса вашего кода и на другие системы, помимо GNU/Linux. Этот стандарт вполне читабелен; он концентрирует в себе десятилетия опыта и хорошей практики.
Выбирайте для работы лучший интерфейс
Если стандартный интерфейс выполняет нужную вам работу, используйте его в своем коде. Для обнаружения недоступного интерфейса используйте Autoconf, и добавьте его замещающую версию для ограниченной системы. (Например, на некоторых более старых системах отсутствует функция memmove()
, которую довольно легко запрограммировать самому или вставить из библиотеки GLIBC).
Изолируйте проблемы переносимости за новыми интерфейсами
Иногда вам может потребоваться выполнить специфичные для операционной системы задачи, которые можно исполнить на одних системах, но нельзя на других. (Например, на некоторых системах каждая программа должна сама раскрывать групповые символы в командной строке, вместо выполнения этой работы командным процессором.) Создайте новый интерфейс, который ничего не делает в системах, которым он не нужен, но проделывает необходимую коррекцию для систем, которые в этом нуждаются.
Используйте для конфигурирования Autoconf
По возможности избегайте #ifdef
. Если это невозможно, скройте его в низкоуровневом библиотечном коде. Для проверки тестов, которые должны исполняться с помощью #ifdef
, используйте Autoconf.
1. The С Programming Language, 2nd edition, by Brian W. Kernighan and Dennis M. Ritchie Prentice-Hall, Englewood Cliffs, New Jersey, USA, 1989. ISBN: 0-13-110370-9[25]25
Русский перевод Брайан Керниган, Денис Ритчи. Язык программирования Си (изд. 3-е, исправленное) Санкт– Петербург. Невский диалект, 2001 – Примеч. науч. ред.
[Закрыть].
Это «библия» С, охватывающая версию стандарта С 1990 г. Это довольно сжатая книга, с большим количеством информации, помещенной в поразительно небольшое число страниц. Вам может потребоваться прочитать ее более одного раза; это стоит затраченных усилий.
2. С, A Reference Manual. 5th edition, by Samuel P. Harbison III and Guy L. Steele, Ji. Prentice-Hall, Upper Saddle River, New Jersey, USA, 2002. ISBN: 0-13-089592-X.
Это тоже классическая книга. Она охватывает оригинальный С, а также стандарты 1990 и 1999 гг. Поскольку она современна, она служит ценным дополнением к первой книге. Она охватывает многие важные темы, такие, как интернациональные типы и библиотечные функции, которых нет в книге Кернигана и Ричи.
3. Notes on Programming in С, by Rob Pike, February 21,1989 Доступна через множество веб-сайтов. Возможно, чаще всего упоминаемым местом является http://www.lysator.liu.se/c/pikestyle.html
. (Многие другие полезные статьи доступны там же на один уровень выше: http://www.lysator.liu.se/с/
.) Роб Пайк много лет работал в исследовательском центре Bell Labs, где были созданы С и Unix, и проводил там изыскания. Его замечания концентрируют многолетний опыт в «философию ясности в программировании», это стоит прочтения.
4. Различные ссылки на http://www.chris-lott.org/resources/cstyle/
. Этот сайт включает заметки Роба Пайка и несколько статей Генри Спенсера (Henry Spencer). Особенно высокое положение занимает «Рекомендуемый стиль С и стандарты программирования» (Recommended С Style and Coding Standards), первоначально написанный на сайте Bell Labs Indian Hill.