КаталогИндекс раздела
НазадОглавлениеВперед


Основы программирования

СОДЕРЖАНИЕ

1. Введение

2. Выбор языка программирования
    2.1. Поддерживаемые языки программирования
        2.1.1. Язык C
        2.1.2. Фортран
        2.1.3. Паскаль
        2.1.4. Ассемблер
    2.2. Специализированные языки программирования
        2.2.1. awk(1)
        2.2.2. lex(1)
        2.2.3. yacc(1)
        2.2.4. m4(1)
        2.2.5. bc(1) и dc(1)
        2.2.6. curses(3X)

3. Когда программа написана
    3.1. Компиляция и редактирование связей
        3.1.1. Компиляция C-программ
        3.1.2. Компиляция Фортран-программ
        3.1.3. Диагностические сообщения при компиляции
        3.1.4. Редактирование внешних связей

4. Интерфейс между языком программирования и ОС UNIX
    4.1. Почему для иллюстрации интерфейса используется язык C
    4.2. Как аргументы передаются в программу
    4.3. Системные вызовы и библиотечные функции
        4.3.1. Классификация системных вызовов и библиотечных функций
        4.3.2. Где можно найти справочную информацию
        4.3.3. Как системные вызовы и библиотечные функции используются в C-программах
    4.4. Включаемые файлы
    4.5. Библиотеки объектных файлов
    4.6. Ввод/вывод
        4.6.1. Стандартные открытые файлы
        4.6.2. Именованные файлы
        4.6.3. Низкоуровневый ввод/вывод и почему не стоит им пользоваться
    4.7. Управление окружением и получение информации о его состоянии
    4.8. Процессы
        4.8.1. system(3s)
        4.8.2. exec(2)
        4.8.3. fork(2)
        4.8.4. Каналы
    4.9. Обработка ошибок
    4.10. Сигналы и прерывания

5. Анализ и отладка
    5.1. Пример программы
    5.2. cflow(1)
    5.3. ctrace(1)
    5.4. cxref(1)
    5.5. lint(1)
    5.6. prof(1)
    5.7. size(1)
    5.8. strip(1)
    5.9. sdb(1)

6. Средства организации разработки программного обеспечения
    6.1. Утилита make(1)
    6.2. Работа с архивами
    6.3. Использование системы SCCS программистами-одиночками


1. ВВЕДЕНИЕ

Данная глава предназначается тем, кто только учится программировать в среде ОС UNIX. По классификации, приведенной в предыдущей главе, это, как правило, программисты-одиночки, не интересующиеся программированием глубоко. Может оказаться, что в данной главе и в связанной с ней справочной информации имеется все, что им необходимо для подготовки и запуска программ на компьютере.

Программистам с более высокими требованиями, например, участвующим в различных прикладных проектах или разрабатывающим программы, которые будут переноситься на другие компьютеры, следует рассматривать эту главу всего лишь как введение.

 

2. ВЫБОР ЯЗЫКА ПРОГРАММИРОВАНИЯ

Какой язык программирования использовать? На этот вопрос можно ответить, например, так: "Я всегда пишу программы на ФОРГОЛе, потому что знаю его лучше всего." В некоторых обстоятельствах это действительно разумное решение. Но если предположить, что Вы знаете более одного языка, что различные языки имеют сильные и слабые стороны, что, зная один язык, относительно легко изучить другой, вопрос выбора языка становится более содержательным. При выборе языка можно попытаться ответить на следующие вопросы:

2.1. Поддерживаемые языки программирования

В данном разделе речь идет о языках программирования, компиляторы для которых поступают как вместе с ОС UNIX, так и независимо. В будущем планируется расширение спектра доступных языков.

2.1.1. Язык C

Язык C тесно связан с ОС UNIX, так как первоначально был разработан именно для реализации ядра операционной системы. Поэтому, в первую очередь, он очень удобен для программирования задач, использующих системные вызовы операционной системы, например, для организации низкоуровнего ввода/вывода, управления памятью или физическими устройствами, организации связи между процессами и т.д. Кроме того, язык C может успешно применяться и для реализации программ, не требующих такого непосредственного взаимодействия с операционной системой. При выборе языка программирования следует иметь в виду следующие характеристики языка C:

Язык C естественным образом ориентирован на структурное программирование. Большие программы подразделяются на функции, которые можно считать отдельно компилируемыми единицами. Кроме облегчения внесения изменений в программы, при таком подходе в наибольшей степени реализуется идеология программирования в ОС UNIX: стараться в максимальной степени использовать уже имеющиеся программы.

Язык C довольно труден в изучении. Чтобы научиться программировать на C, используя все его возможности, необходимо несколько месяцев интенсивной практики. Поэтому, если Вы программируете лишь эпизодически, лучше выбрать какой-нибудь другой, более простой язык.

2.1.2. Фортран

Несмотря на то, что Фортран является старейшим из языков высокого уровня, он и сейчас успешно применяется для математических расчетов. Так, на Фортране удобно программировать задачи статистического анализа и другие научные приложения. Основной целью при проектировании языка было достижение высокой эффективности при выполнении программ. Цель была достигнута, но для этого пришлось в некоторой степени пожертвовать гибкостью языка. Например, имеется только один вид оператора цикла. Кроме того, тексты программ должны иметь довольно жесткий формат. Отметим, правда, что использование препроцессоров несколько сглаживает указанные недостатки.

2.1.3. Паскаль

Поскольку Паскаль первоначально проектировался как язык для обучения программированию, он прост в обращении и получил широкое признание. Кроме простоты, к достоинствам языка можно отнести высокую степень структурированности программ и возможность обращения к системным вызовам (аналогично языку C). Удобнее всего использовать Паскаль для небольших программ, что естественно объясняется его первоначальным предназначением. Недостатками языка являются, например, отсутствие возможности инициализации переменных и недостаточные средства для работы с файлами.

2.1.4. Ассемблер

Этот язык наиболее близок к аппаратуре и поэтому является машинно-зависимым и не является мобильным. Необходимость программирования на ассемблере возникает, как правило, при невозможности воспользоваться языками высокого уровня.

2.2. Специализированные языки программирования

Кроме описанных языков программирования, в ОС UNIX имеются спе- циализированные языки, перечисленные ниже.

2.2.1. awk(1)

awk(1) (буквы в названии языка представляют инициалы его авторов) отыскивает во входном файле строки, соответствующие шаблону, описанному в файле спецификаций. Найдя такую строку, awk выполняет действия, также заданные в файле спецификаций. Зачастую программа из двух строк на awk может сделать то же, что и программа на две страницы на таких языках как C или Фортран. В качестве примера рассмотрим следующую задачу. Пусть имеется множество записей, состоящих из двух полей, первое из которых является ключом, а второе содержит число. Требуется каждую группу записей с одним и тем же ключевым значением заменить одной записью с тем же ключом и с числом, равным сумме чисел во всех записях группы. Таким образом, в результирующем наборе записей уже не будет совпадающих значений ключа. Если предположить, что исходный набор записей отсортирован по ключевому полю, то алгоритм решения задачи может выглядеть, например, так:

Прочитать первую запись в рабочую область.

Читать записи, пока не встретится признак конца файла (EOF); при этом:

Если ключ текущей записи совпадает с ключом записи в рабочей области, прибавить к числу в рабочей области число из текущей записи. В противном случае добавить запись из рабочей области к результату и поместить в рабочую область текущую запись.

Если достигнут конец файла, добавить к результату последнюю запись из рабочей области.

Программа на awk, выполняющая те же действия, может быть, например, такой:

               { qty[$1] += $2 }
       END     { for (key in qty) print key, qty[key] }

Отметим, что в отличие от описанного выше алгоритма, для этой программы не требуется, чтобы входной файл был отсортирован.

2.2.2. lex(1)

С помощью lex(1) можно сгенерировать C-программу, которая распознает во входном потоке регулярные выражения, специфицированные пользователем, после чего выполняет некоторые действия, также заданные пользователем, и формирует выходной поток для следующей программы.

2.2.3. yacc(1)

С помощью yacc(1) можно сгенерировать C-программу для выполнения синтаксического анализа входного потока согласно правилам, заданным в файле спецификаций. Кроме описания грамматики, этот файл содержит описания действий, которые должны быть выполнены, когда лексемы во входном потоке сопоставляются с тем или иным правилом грамматики. Для выделения лексем во входном потоке удобно использовать lex.

2.2.4. m4(1)

m4(1) - это макропроцессор, который можно использовать как препроцессор для языка ассемблера и C.

2.2.5. bc(1) и dc(1)

bc(1) позволяет использовать терминал как программируемый калькулятор. С помощью bc можно выполнить арифметические вычисления, специфицированные в файле или поступающие со стандартного ввода. В действительности bc является препроцессором к dc(1). Можно пользоваться непосредственно dc, но это трудно, поскольку он работает с обратной польской записью.

2.2.6. curses(3X)

Хотя в действительности это библиотека C-функций, curses(3X) упоминается здесь, поскольку его функции можно рассматривать как подъязык для выполнения операций с экраном терминала. Если Вы пишете программы, предполагающие организацию экранного интерфейса с пользователем, Вам будет полезно знать возможности этого пакета.

В заключении краткого обзора, напомним, что не следует упускать возможность использовать shell-процедуры.

 

3. КОГДА ПРОГРАММА НАПИСАНА

Как правило, при компиляции программ в среде ОС UNIX в качестве последних двух фаз выполняются ассемблирование и редактирование внешних связей. При этом ассемблерный текст, то есть результат предыдущих фаз компиляции, транслируется в команды машинного языка компьютера, на котором программа будет выполняться. Редактор внешних связей разрешает все неопределенные ссылки и делает объектный файл выполняемым. Для большинства языков программирования в ОС UNIX ассемблер и редактор связей генерируют Объектные Файлы Обычного Формата. В результате утилиты, обрабатывающие объектные файлы, могут работать на разных компьютерах под управлением различных версий ОС UNIX, поскольку форматы объектных файлов совпадают. Объектный файл обычного формата содержит:

Объектный файл состоит из секций. Обычно их по крайней мере две: .text и .data. В некоторых объектных файлах может быть также секция .bss, содержащая неинициализированные данные. Название этой секции происходит от мнемоники ассемблерной псевдокоманды bss (block started by symbol). Используя опции компиляторов, можно добиться включения в объектный файл дополнительной информации. Например, при компиляции с опцией -g добавляются номера строк и другая информация, необходимая для символьной отладки. Даже занимаясь программированием в течение многих лет, можно особенно не задумываться о содержимом и организации объектных файлов обычного формата, поэтому мы не будем сейчас останавливаться на этом вопросе. Обычному формату объектных файлов посвящена отдельная глава.

3.1. Компиляция и редактирование связей

Команда, с помощью которой можно выполнить компиляцию, зависит от используемого языка программирования. Например:

Настоятельно рекомендуется выполнять компиляцию и редактирование связей в рамках Интегрированной Среды Разработки Программ (ИСРП). ИСРП, в зависимости от расширения имени файла, вызовет нужный компилятор или редактор связей. Файлы с исходными текстами C-программ должны иметь расширение .c, Фортран-файлы - расширение .for, файлы, управляющие процессом редактирования внешних связей, - .bld. Выгода от использования ИСРП состоит в том, что не нужно помнить опции команд запуска компиляторов или библиотеки, необходимые для редактирования внешних связей.

В рамках ИСРП функционирует отладчик КРОТ. Чтобы воспользоваться его услугами, компиляцию и редактирование связей нужно производить с опцией -krot.

3.1.1. Компиляция C-программ

Файлы с исходными текстами C-программ должны иметь расширение .c, например: mycode.c. Команда вызова компилятора имеет следующий вид:

       cc mycode.c

При успешном исходе компиляции после нее будет выполнено редактирование связей и сгенерирован выполняемый файл a.out.

Для управления процессом компиляции и редактирования связей команда cc(1) имеет несколько опций. Перечислим наиболее употребительные из них:

-c
  Подавляется фаза редактирования связей. В этом случае генерируется объектный файл (в нашем примере mycode.o), который позже может быть использован для редактирования связей с помощью команды cc без опции -c.
-g
  Генерируется дополнительная информация о переменных и операторах языка для символьной отладки. Если Вы планируете отлаживаться в рамках ИСРП, используйте вместо -g опцию -krot.
  Объектная программа подвергается оптимизации. В результате применения данной опции сокращается размер объектного файла и увеличивается скорость выполнения. Эта опция логически несовместима с опцией -g. Обычно она используется, когда программа уже отлажена.
-p
  Объектная программа допускает использование утилиты prof(1) для получения временного профиля выполнения. Удобно использовать эту опцию для выявления процедур, реализация которых требует совершенствования.
-o вых_файл
  Выполняемый файл, полученный после редактирования связей, будет иметь имя вых_файл, а не a.out.

Остальные опции, используемые с командой cc, описаны в Справочнике пользователя.

Если имя файла, указанного в команде cc, оканчивается на .s, считается, что в этом файле содержится программа на языке ассемблера. В результате пропускаются все фазы, предшествующие ассемблированию.

3.1.2. Компиляция Фортран-программ

Файлы с исходными текстами Фортран-программ должны иметь расширение .for, для вызова компилятора служит команда svs(1). При необходимости воспользоваться отладчиком следует указывать опцию -krot.

3.1.3. Диагностические сообщения при компиляции

C-компилятор генерирует сообщения для тех операторов программы, которые не удалось скомпилировать. Как правило, смысл этих сообщений очевиден, но, как и в большинстве компиляторов для других языков, они часто указывают не сам ошибочный оператор, а оператор, расположенный на некотором расстоянии от него. Например, если Вы случайно поставите точку с запятой в конце условия оператора if, то последующий подоператор else будет отмечен как синтаксическая ошибка. В случае, когда между if и else имеется блок из нескольких операторов, выданный компилятором номер строки синтаксической ошибки будет существенно отличаться от истинного. Другим распространенным источником синтаксических ошибок являются несбалансированные фигурные скобки.

3.1.4. Редактирование внешних связей

Команда ld(1) вызывает редактор внешних связей непосредственно. Однако обычно эта команда не используется. Как правило, применяется команда запуска системы компиляции того или иного языка (например, cc), которая сама вызывает редактор связей. Редактор связей объединяет несколько объектных файлов в один, выполняет настройку ссылок, включает процедуры инициализации и генерирует таблицу имен, используемую отладчиком. Разумеется, можно выпол- нять редактирование связей и в случае единственного объектного файла. Результат редактирования связей по умолчанию помещается в файл a.out.

Файлы, указанные в команде ld и не являющиеся объектными (имена объектных файлов обычно оканчиваются на .o), рассматриваются как архивные библиотеки или файлы, содержащие директивы редактора связей. Команда ld имеет 16 опций, мы опишем четыре из них.

-o вых_файл
  Используя эту опцию, можно сообщить редактору связей имя файла, которое следует использовать вместо a.out. Здесь нужно указать имя, по которому вы хотите в будущем вызывать Вашу программу. Конечно, можно достигнуть того же результата, выполнив команду
      mv a.out progname
-l библ
  Редактор связей будет использовать библиотеку с именем libбибл.a, где библ является цепочкой символов длиной не более 9. Данная опция позволяет дополнить список просматриваемых библиотек. Например, библиотека libc.a используется по умолчанию при вызове редактора связей посредством cc, а математическая библиотека libm.a, если это необходимо, должна быть указана с помощью данной опции.
Опция -l может встречаться в команде ld несколько раз с различными значениями библ. Поскольку библиотека просматривается, когда встречается ее имя, порядок указания опций -l существен. Наиболее безопасно указывать опцию -l в конце командной строки.
Опция -l связана а опцией -L.
-L каталог
  Последовательность поиска библиотек вида libбибл.a изменяется следующим образом: сначала производится поиск в указанном каталоге, а затем в каталогах, принимаемых по умолчанию, обычно /lib и /usr/lib.
Опцию удобно применять в случае, если у Вас имеется несколько версий библиотеки и Вы хотите использовать одну из них. Если библиотека найдена в указанном с помощью опции -L каталоге, ее поиск в других каталогах не производится. Поскольку опция -L влияет на поиск библиотек, указанных с помощью опции -l, в командной строке она должна предшествовать -l.
-u имя
  В таблицу имен заносится объект имя как неопределенный. Такая возможность полезна, когда загружаются только библиотечные файлы, поскольку в начальный момент таблица имен пуста и нужна какая-либо неразрешенная ссылка, чтобы начать загрузку первой подпрограммы.

Если редактор связей вызывается посредством команды cc, в выполняемый файл включается процедура инициализации (обычно для C-программ это /lib/crt0.o). После выполнения главной программы процедура инициализации обращается к системному вызову exit(2).

Редактор связей воспринимает файлы, содержащие директивы для него. Подробному описанию управляющего языка редактора связей посвящена отдельная глава.

 

4. ИНТЕРФЕЙС МЕЖДУ ЯЗЫКОМ ПРОГРАММИРОВАНИЯ И ОС UNIX

Выполнение программы на компьютере зависит от различных особенностей операционной системы. Некоторые из них, например загрузка программы в основную память или инициализация выполнения, с точки зрения программы не видны. Они, в сущности, планируются редактором связей заранее, когда он помечает объектный файл как выполняемый. Программистам редко приходится иметь дело с этими вопросами непосредственно.

Однако другие вопросы, такие как ввод/вывод, действия с файлами, выделение памяти требуют участия программиста. Взаимодействие между программой и операционной системой обычно называется интерфейсом между ними. В данном разделе освещаются следующие темы:

4.1. Почему для иллюстрации интерфейса используется язык C

Везде далее в этом разделе для иллюстрации интерфейса между языком программирования и операционной системой используется язык C, поскольку механизмы интерфейса ориентированы на этот язык больше, чем на любой другой язык высокого уровня. Таким образом, в данном разделе в действительности описывается интерфейс между ОС UNIX и языком C.

4.2. Как аргументы передаются в программу

Информация или управляющие параметры могут передаваться в программу как аргументы командной строки при запуске программы. При этом указанные в командной строке аргументы передаются функции main() через два ее параметра, первый из которых содержит количество аргументов, а второй является массивом указателей на цепочки символов, содержащие передаваемую в качестве аргументов информацию (выполнение любой C-программы начинается с функции с именем main). Заметим, что поскольку количество аргументов программы передается ей во время запуска, программа не обязана знать заранее, сколько аргументов следует ожидать.

Обычно параметры функции main() имеют имена argc и argv, хотя это и не обязательно. argc - целое число, равное количеству передаваемых аргументов. Это число всегда больше или равно 1, поскольку сама команда считается первым аргументом, и argv[0] является указателем на цепочку символов, представляющую команду. Остальные элементы массива argv - это указатели на цепочки символов содержащие аргументы. Все упомянутые цепочки символов оканчиваются нулевым байтом.

Если во время запуска программы ей будут передаваться аргументы, следует в тексте программы предусмотреть их обработку. Передаваемая информация может, например, содержать:

Приведем примеры фрагментов программ, иллюстрирующих эти два способа использования аргументов программы.

       #include <stdio.h>
       #define FALSE   0
       #define TRUE    1
       
       main (argc, argv)
         int argc;
         char *argv [];
       {
         void exit ();
         int oflag = FALSE;
         int pflag = FALSE; /* Функциональные флаги */
         int rflag = FALSE;
         int ch;
       
         while ((ch = getopt (argc, argv, "opr")) != EOF) {
           /* Если имеются опции, присвоить флагам TRUE.
              Если опций нет, вывести сообщение об ошибке */
           switch (ch) {
             case 'o':
               oflag = TRUE;
               break;
             case 'p':
               pflag = TRUE;
               break;
             case 'r':
               rflag = TRUE;
               break;
             default:
               (void) fprintf (stderr,
        "Использование: %s [-opr]\n", argv [0]);
               exit (2);
           }
         }
         . . .
       }

       #include <stdio.h>
       
       main (argc, argv)
         int argc;
         char *argv [];
       {
         FILE *fopen (), *fin;
         void perror (), exit ();
       
         if (argc > 1) {
           if ((fin = fopen (argv [1], "r")) == NULL) {
             /* Первое %s - для вывода имени программы.
       Второе %s - для вывода имени файла,
       который не удалось открыть */
             (void) fprintf (stderr,
               "%s: неудача при попытке открыть файл %s: ",
               argv [0], argv [1]);
             perror ("");
             exit (2);
           }
         }
         . . .
       }

При разборе shell'ом командной строки аргументами считаются любые последовательности непробельных символов, разделенные пробелами или знаками табуляции. Последовательность символов, заключенных в двойные кавычки (например, "abc def"), считается одним аргументом, даже если в ней встречаются пробелы или табуляции. Разумеется, проверка корректности передаваемых аргументов полностью возлагается на программиста.

Кроме argc и argv, у функции main() есть еще один параметр, обычно обозначаемый как envp. Это массив указателей на цепочки символов, образующих окружение. Более подробная информация о envp имеется в Справочнике программиста в статьях exec(2) и environ(5).

4.3. Системные вызовы и библиотечные функции

Системные вызовы - это запросы программы к ядру операционной системы на выполнение некоторых действий. Библиотечные функции - это заранее запрограммированные модули, которые используются для поддержки некоторых возможностей языка программирования.

Внешне обращения к системным вызовам и библиотечным функциям ничем не отличаются и выглядят как вызовы обычных функций языка. Однако некоторые отличия все же имеются:

Это означает, что несмотря на то, что библиотечные функции увеличивают размер выполняемого файла, накладные расходы на переключение при их выполнении меньше, чем при выполнении системных вызовов.

4.3.1. Классификация системных вызовов и библиотечных функций

Системные вызовы можно достаточно естественно разделить на следующие категории:

Библиотечные функции можно разделить на категории в зависимости от разделов Справочника программиста, в которых находятся их описания. Однако первая часть раздела 3 (3C и 3S) содержит описания весьма большого числа функций, и имеет смысл провести дальнейшую классификацию.

В следующей таблице приведен список функций стандартного ввода/вывода. Все они описаны в разделе 3 Справочника программиста. В Справочнике часто в одной статье описывается не одна, а несколько связанных функций. В таблице приводится соответствие названий статей и описываемых в них функций: в левой колонке находится название статьи, а все остальные имена в данной стро- ке - это имена функций, описываемых в статье.

Имена функций Назначение
fclose fflush  Закрыть поток или вытолкнуть его буфера.
ferror feof clearerr fileno  Опрос состояния потока.
fopen freopen fdopen  Открыть поток.
fread fwrite  Двоичный ввод/вывод.
fseek rewind ftell  Установка текущей позиции потока.
getc getchar fgetc getw  Считывание символа или слова из потока.
gets fgets  Считывание цепочки символов из потока.
popen pclose  Создание и ликвидация канала между программой и командой.
printf fprintf sprintf  Вывод с преобразованием по формату.
putc putchar fputc putw  Запись в поток символа или слова.
puts fputs  Запись в поток цепочки символов.
scanf fscanf sscanf  Ввод с преобразованием по формату.
setbuf setvbuf  Назначение буферов для потока.
system  Выполнение команды shell'а.
tmpfile  Создание временного файла.
tmpnam tempnam  Создание имен временных файлов.
ungetc  Вставка символа в поток ввода.
vprintf vfprintf vsprintf  Форматный вывод списка аргументов, заданного по правилам varargs.

При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

       #include <stdio.h>

Следующая таблица содержит список функций, предназначенных для обработки цепочек символов. В Справочнике программиста они описаны в одной статье string(3C).

Операции над цепочками символов

Имена функций Назначение
strcat (s1, s2) Добавить копию s2 к концу s1.
strncat (s1, s2, n) Добавить n символов из s2 к концу s1.
strcmp (s1, s2) Сравнить две цепочки символов. Возвращает целое число, меньшее, большее или равное нулю в зависимости от того, предшествует ли s1 лексикографически s2, следует за ней или совпадает с ней.
strncmp (s1, s2, n) Сравнить n символов из двух цепочек.
strcpy (s1, s2) Копировать символы из s2 в s1 до тех пор, пока не будет скопирован нулевой байт (\0).
strncpy (s1, s2, n) Скопировать n символов из s2 в s1. Если s2 содержит более n символов, она будет усечена, если меньше n - в s1 будут добавлены нулевые байты.
strdup (s) Возвращает указатель на новую цепочку символов, являющуюся копией s.
strchr (s, c) Возвращает указатель на первое вхождение символа c в цепочку s, или NULL, если s не содержит c.
strrchr (s, c) Возвращает указатель на последнее вхождение символа c в цепочку s, или NULL, если s не содержит c.
strlen (s) Возвращает число символов в s до ближайшего нулевого байта.
strpbrk (s1, s2) Возвращает указатель на первое вхождение в s1 какого-либо символа из s2, либо NULL, если s1 не содержит ни одного символа из s2.
strspn (s1, s2) Возвращает длину начального фрагмента s1, состоящего только из символов, содержащихся в s2.
strcspn (s1, s2) Возвращает длину начального фрагмента s1, состоящего только из символов, не содержащихся в s2.
strtok (s1, s2) Находит включение символов из s2 в s1.

При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

       #include <string.h>

Включаемый файл <string.h> содержит внешние описания функций обработки цепочек символов.

Следующая таблица содержит список макросов, предназначенных для классификации ASCII-символов. В Справочнике программиста они описаны в статье ctype(3C).

Классификация символов

Имена функций Назначение
isalpha (c) c - буква?
isupper (c)  c - большая буква?
islower (c)  c - малая буква?
isdigit (c)   c - цифра: [0-9]?
isxdigit (c)  c - шестнадцатеричная цифра: [0-9], [A-F] или [a-f]?
isalnum (c)   c - алфавитно-цифровой символ (буква или цифра)?
isspace (c)   c - пробел, табуляция, возврат каретки, перевод строки, вертикальная табуляция или символ перехода к новой странице?
ispunct (c)   c - знак пунктуации (то есть не управляющий и не алфавитно-цифровой символ)?
isprint (c)   c - печатный символ? [Коды таких символов располагаются в диапазоне от 040 (пробел) до 0176 (тильда).]
isgraph (c)   c - печатный символ, но не пробел?
iscntrl (c)  c - управляющий символ (код меньше 040) или символ забоя (0177)?
isascii (c)   c является ASCII-символом (код меньше 0200)?

При использовании всех функций, перечисленных в этой таблице, необходимо включить в программу оператор

       #include <ctype.h>

Эти функции возвращают отличное от нуля значение, если указанное в правой части таблицы условие истинно, в противном случае возвращается нуль.

Следующие две таблицы содержат список функций и макросов, используемых для преобразования символов, целых чисел или цепочек символов из одного представления в другое.

Имена функций Назначение
ecvt fcvt gcvt  Преобразование вещественного числа в цепочку символов.
l3tol ltol3  Преобразование 3-байтного целого числа в длинное целое и обратно.
strtod atof  Преобразование цепочки символов в вещественное число двойной точности.
strtol atol atoi  Преобразование цепочки символов в целое число.

conv(3C): Преобразование символов

toupper   Функция преобразования малой буквы в большую.
_toupper  Макрос преобразования малой буквы в большую.
tolower   Функция преобразования большой буквы в малую.
_tolower  Макрос преобразования большой буквы в малую.
toascii   Обнуляет у аргумента все биты, не являющиеся частью стандартного ASCII-символа; предназначен для достижения совместимости с другими системами.

При использовании макросов из последней таблицы необходимо включить в программу оператор

       #include <ctype.h>

4.3.2. Где можно найти справочную информацию

Системные вызовы описаны в алфавитном порядке в разделе 2 Справочника программиста. Информация о библиотечных функциях содержится в разделе 3. В данном Руководстве выше были описаны функции из первого подраздела раздела 3. В остальных его подразделах имеется информация о:

4.3.3. Как системные вызовы и библиотечные функции используются в C-программах

Как правильно пользоваться системными вызовами и библиотечными функциями, объясняется в соответствующих статьях Справочника программиста. Чтобы чтение Справочника было наиболее эффективным, необходимо знать типичную структуру его статей. Рассмотрим, например, статью gets(3S).

GETS(3S) GETS(3S)
НАЗВАНИЕ
  gets, fgets - чтение цепочки символов из потока
СИНТАКСИС
 
       #include <stdio.h>
       
       char *gets (s)
       char *s;
       
       char *fgets (s, n, stream)
       char *s;
       int n;
       FILE *stream;
ОПИСАНИЕ
 

Функция gets читает символы из стандартного потока ввода stdin в область памяти, на которую указывает аргумент s. Чтение производится до тех пор, пока не встретится перевод строки или конец файла. Символ перевода строки отбрасывается, а прочитанная цепочка ограничивается нулевым байтом.

Функция fgets считывает (n-1) символов из потока ввода stream в область памяти, на которую указывает аргумент s. Чтение производится до тех пор, пока не встретится перевод строки (в отличие от gets он не отбрасывается) или конец файла. Прочитанная цепочка символов ограничивается нулевым байтом.

СМ. ТАКЖЕ
  ferror(3S), fopen(3S), fread(3S), getc(3S), scanf(3S).
ДИАГНОСТИКА
  Если первым прочитанным символом окажется признак конца файла, то есть фактически ни одного символа не будет считано, то обе функции возвращают пустой указатель NULL. Если обнаружена ошибка чтения, например, при попытке использовать эти функции для файлов, не открытых на чтение, то также возвращается NULL. В остальных случаях возвращается значение указателя s.

В этом примере в одной статье описываются две связанные функции: gets() и fgets(). Обе функции считывают цепочку символов из потока, но делают это несколько по-разному. В разделе ОПИСАНИЕ объясняется, как действует каждая из них.

Раздел СИНТАКСИС содержит информацию о том, как нужно обращаться к описываемым функциям из программы. Заметим, что в первой строке этого раздела записано:

       #include <stdio.h>
Это означает, что программе, использующей функции gets() или fgets(), необходима информация из включаемого файла стандартного ввода/вывода. Обычно оператор #include помещается в начале исходного текста. Ниже будет приведена версия файла <stdio.h>, которую можно просмотреть, чтобы понять, что используется функциями gets() и fgets(). Далее в разделе СИНТАКСИС приводится формальное описание функций. Из формального описания можно узнать:

Мы рассмотрели простой пример описания функции gets(). Если Вы хотите проверить себя на более сложном примере, попытайтесь понять значение различных элементов описания функции fgets().

Рассматривая функцию fgets(), мы сталкиваемся еще с одной особенностью языка C. Третий аргумент этой функции - stream - есть файл с ассоциированными с ним буферами, описанный как указатель на производный тип FILE. Где определяется этот тип? Правильно! В файле <stdio.h>.

Завершая обсуждение способов вызова функций, описанных в Справочнике программиста, приведем пример фрагмента программы, в котором вызывается функция gets():

       #include <stdio.h>
       
       main ()
       {
         char sarray [80];
       
         for (;;) {
           if (gets (sarray) != NULL) {
               . . .
             /* Выполнить что-либо с цепочкой символов */
               . . .
           }
         }
       }

Можно задать вопрос: "Откуда функция gets() считывает символы?". Ответ: "Со стандартного ввода". Под стандартным вводом обычно понимается то, что вводится с клавиатуры терминала, на котором была набрана команда, запустившая выполнение программы, или вывод другой программы, направленный функции gets(). То, что функция gets() считывает информацию со стандартного ввода, можно узнать из раздела ОПИСАНИЕ в статье Справочника. Действительно, там написано: "Функция gets читает символы из стандартного потока ввода...". Стандартный поток ввода определен в файле <stdio.h>.

Ниже приводится содержимое файла <stdio.h>:

       #ifndef _NFILE
       #define _NFILE  20

       #define BUFSIZ  1024

       /* Размер буфера при выводе в небуферизованный файл */
       #define _SBFSIZ 8

       typedef struct {
        int     _cnt;
        unsigned char *_ptr;
        unsigned char *_base;
        char    _flag;
        char    _file;
       } FILE;

       /* Флаг  _IOLBF означает, что файловый вывод будет буфе-
          ризоваться построчно. _IONBF, _IOLBF и  _IOFBF  могут
          использоваться не только как флаги, но и как значения
          "типа" для передачи функции setvbuf */
       #define _IOFBF      0000
       #define _IOREAD     0001
       #define _IOWRT      0002
       #define _IONBF      0004
       #define _IOMYBUF    0010
       #define _IOEOF      0020
       #define _IOERR      0040
       #define _IOLBF      0100
       #define _IORW       0200

       #ifndef NULL
       #define NULL        0
       #endif
       #ifndef EOF
       #define EOF(-1)
       #endif

       #define stdin       (&_iob[0])
       #define stdout      (&_iob[1])
       #define stderr      (&_iob[2])

       #define _bufend(p)  _bufendtab[(p)->_file]
       #define _bufsiz(p)  (_bufend(p) - (p)->_base)

       #ifndef lint
       #define getc(p)     (--(p)->_cnt < 0 ? _filbuf(p) : \
         (int) *(p)->_ptr++)
       #define putc(x, p)  (--(p)->_cnt < 0 ? \
         _flsbuf((unsigned char) (x), (p)) : \
         (int) (*(p)->_ptr++ = \
         (unsigned char) (x)))
       #define getchar()   getc(stdin)
       #define putchar(x)  putc((x), stdout)
       #define clearerr(p) ((void) ((p)->_flag &= \
         ~(_IOERR | _IOEOF)))
       #define feof(p)     ((p)->_flag & _IOEOF)
       #define ferror(p)   ((p)->_flag & _IOERR)
       #define fileno(p)   (p)->_file
       #endif

       extern FILE  _iob[_NFILE];
       extern FILE  *fopen(), *fdopen(), *freopen(), *popen(),
           *tmpfile();
       extern long  ftell();
       extern void  rewind(), setbuf();
       extern char  *ctermid(), *cuserid(), *fgets(), *gets(),
           *tempnam(), *tmpnam();
       extern int   fclose(), fflush(), fread(), fwrite(),
           fseek(), fgetc(), getw(), pclose(), printf(),
           fprintf(), sprintf(), vprintf(), vfprintf(),
           vsprintf(), fputc(), putw(), puts(), fputs(),
           scanf(), fscanf(), sscanf(), setvbuf(),
           system(), ungetc();
       extern unsigned char *_bufendtab[];

       #define L_ctermid  9
       #define L_cuserid  9
       #define P_tmpdir   "/usr/tmp/"
       #define L_tmpnam   (sizeof (P_tmpdir) + 15)
       #endif

4.4. Включаемые файлы

В предыдущих разделах данной главы часто упоминался файл <stdio.h>, был также приведен его полный текст. <stdio.h> - это наиболее часто используемый включаемый файл при программировании на C в ОС UNIX. Разумеется, существует много других включаемых файлов.

Включаемые файлы содержат определения и описания, одновременно используемые более чем одной функцией. Как правило, имена включаемых файлов имеют расширение .h. Содержимое включаемых файлов обрабатывается препроцессором языка C во время компиляции. Для указания препроцессору о необходимости включения файла применяется директива #include, которую нужно поместить в текст программы. Вообще, директивой препроцессора считаются строки программы, начинающиеся с символа #. Чаще всего используются директивы #include и #define. Как уже говорилось, директива #include используется для вызова (и обработки) содержимого указанного в ней включаемого файла. Директива #define указывает препроцессору, что в тексте программы необходимо заменить каждое вхождение определяемого имени на цепочку лексем. Например, директива

       #define _NFILE 20

устанавливает максимальное допустимое количество файлов, одновременно открытых программой, равным 20. Полный список директив препроцессора приведен в статье cpp(1).

В тексте Справочника программиста упоминается около 45 различных включаемых файлов. При этом всегда в директиве #include имя включаемого файла изображается в угловых скобках <>. Пример:

       #include <stdio.h>

Угловые скобки в этом случае обозначают, что препроцессор будет считать, что включаемый файл расположен в определенном каталоге. Как правило, таким каталогом является /usr/include. Если Вы хотите какие-либо собственные определения или описания сделать доступными для нескольких файлов, Вы можете создать .h-файл с помощью любого редактора, поместить его в подходящий каталог, и указать его имя в директиве #include следующим образом:

       #include "../defs/rec.h"

В данном случае необходимо указать в кавычках ("") относительное маршрутное имя файла. Указывать полное маршрутное имя файла не рекомендуется, так как в этом случае могут возникнуть трудности при переносе программ, а также другие организационные проблемы. Чтобы не указывать полные маршрутные имена, можно при компиляции программ пользоваться опцией препроцессора -Iкаталог. Если указана эта опция, препроцессор разыскивает включаемые файлы, имена которых указаны в кавычках, следующим образом. Сначала производится поиск в каталоге, в котором расположен компилируемый файл, затем в каталогах, указанных в опции (опциях) -I, и, наконец, в каталогах из стандартного списка. Заметим также, что все включаемые файлы, имена которых указаны в угловых скобках <>, сначала отыскиваются в списке каталогов, заданных с помощью опции -I, а затем в каталогах из стандартного списка.

4.5. Библиотеки объектных файлов

В ОС UNIX объектные файлы часто объединяют в архивы (библиотеки); по соглашению, имена библиотек имеют расширение .a. Например, в библиотеке libc.a хранятся объектные файлы системных вызовов, описанных в разделе 2 Справочника программиста, а также функций (именно функций, а не макросов), описанных в подразделах 3S и 3C раздела 3. Как правило, библиотека libc.a находится в каталоге /lib. Кроме /lib, часто используется каталог /usr/lib, куда помещают прикладные библиотеки.

Во время редактирования связей в выполняемый файл подгружаются копии объектных модулей из архива. Если редактирование связей выполняется по команде cc, требуемые модули по умолчанию отыскиваются в библиотеке libc.a. Если нужно, чтобы поиск производился в библиотеке libбибл.a, отличной от просматриваемой по умолчанию, следует явно указать нужную библиотеку с помощью опции -lбибл. Например, если Ваша программа использует для управления экраном функции из пакета curses(3X), указание опции

       -lcurses
приведет к тому, что редактор связей будет просматривать библиотеки /lib/libcurses.a или /usr/lib/libcurses.a и использовать для разрешения ссылок в Вашей программе ту из них, которую найдет первой.

Чтобы изменить порядок просмотра архивных библиотек, можно использовать опцию -Lкаталог. Если в командной строке опция -L указана перед опцией -l, то редактор связей сначала будет искать указанную в опции -l архивную библиотеку в заданном каталоге, а уже затем в /lib и /usr/lib. Это особенно удобно при тестировании новой версии функции, уже существующей в стандартном архиве. Использование различных версий библиотек возможно, поскольку, разрешив однажды ссылку, редактор связей прекращает дальнейший поиск. Именно поэтому опция -L должна предшествовать опции -l в командной строке.

4.6. Ввод/вывод

Ввод/вывод уже упоминался в данной главе в связи с системными вызовами и библиотечными функциями. Стандартный пакет ввода/вывода языка C состоит из целого множества библиотечных функций. Кроме того, имеется несколько системных вызовов, предназначенных для организации ввода/вывода. Далее вопросы ввода/вывода в языке C будут обсуждаться несколько более подробно. К таким вопросам можно отнести:

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

4.6.1. Стандартные открытые файлы

Программы могут иметь несколько открытых файлов одновременно. Максимально допустимое число открытых в программе файлов может быть различным для разных систем; как правило, оно равно 20. Число файлов, открытых для выполнения операций стандартного ввода/вывода, задается константой _NFILE во включаемом файле <stdio.h>.

Обычно имеется три стандартных открытых файла. В файле <stdio.h>, примерно в середине его текста, имеются три директивы #define, устанавливающие stdin, stdout и stderr равными адресам _iob[0], _iob[1] и _iob[2] соответственно. Массив _iob содержит информацию о потоках, открытых программой. Номер элемента массива _iob называется также дескриптором файла. По умолчанию в ОС UNIX все три стандартных открытых файла ассоциированы с терминалом.

Так как эти файлы постоянно открыты, то при использовании функций и макросов для выполнения операций ввода/вывода со стандартными потоками ввода и вывода stdin и stdout нет необходимости открывать и закрывать файлы. Например, упоминавшаяся выше функция gets() читает символы из потока стандартного ввода stdin; функция puts() записывает завершающуюся нулевым байтом цепочку символов в поток стандартного вывода stdout. Имеются и другие функции для работы с этими потоками, например, для организации посимвольного или форматного ввода/вывода и другие. Кроме упомянутых функций, имеются также функции, выполняющие операции ввода/вывода с потоками, отличными от stdin и stdout. К таким функциям относится, например, fprintf(), которая выполняет те же действия, что и функция printf(), но вывод при этом направляется в указанный поток, например, stderr. Для того, чтобы при выполнении программы операции чтения и записи выполнялись с нужными файлами, можно также воспользоваться средствами shell'а. В заключении приведем пример указания потоков ввода и вывода. Пусть требуется отделить сообщения об ошибках и обычный вывод программы, направляемый в stdout. Это может потребоваться, например, когда вывод программы будет использоваться в качестве ввода для другой программы. В таком случае для организации обычного вывода и выдачи сообщений можно использовать различные модификации одной и той же функции из пакета стандартного ввода/вывода, одна из которых направляет вывод в stdout, а другая - в указанный в качестве ее аргумента поток.

4.6.2. Именованные файлы

Чтобы осуществлять операции ввода/вывода с потоками, отличными от stdin, stdout и stderr, необходимо предварительно открыть их. Это можно проделать с помощью стандартной библиотечной функции fopen(). Получив в качестве аргумента маршрутное имя файла (то есть имя, под которым файл зарегистрирован в файловой системе ОС UNIX), функция fopen() организует поток, ассоциированный с этим файлом, и возвращает указатель на структуру типа FILE. Указатель впоследствии будет передаваться функциям, выполняющим запись и чтение.

Тип FILE определен во включаемом файле <stdio.h>. Чтобы открыть поток, в Вашей программе должна быть соответствущая директива #include и описание вида

       FILE *fin;

Описание говорит о том, что fin является указателем на структуру типа FILE. Чтобы связать конкретный файл с таким указателем, то есть открыть файл, ассоциировать с ним поток и поместить в адресуемую указателем структуру информацию об этом потоке, нужно включить в программу оператор, подобный следующему:

       fin = fopen ("filename", "r");

где filename - это маршрутное имя открываемого файла. "r" означает, что файл открывается на чтение. Этот аргумент называется обычно режимом доступа к файлу. Как можно догадаться, имеются режимы чтения, записи, а также одновременно чтения и записи. Так как попытка открыть файл может завершиться неудачей, вызов функции fopen(), как правило, включают в условный оператор. Пример:

       if ((fin = fopen ("filename", "r")) == NULL)
         (void) fprintf (stderr,
         "%s: Неудача при открытии файла %s\n",argv[0],"filename");

Здесь используется тот факт, что функция fopen() возвращает NULL в случае, если указанный файл не удается открыть. Если файл удалось открыть, в последующих операциях ввода/вывода для ссылки на этот файл используется указатель fin. Пример:

       int c;
       c = getc (fin);

В этом примере макрос getc() считывает из потока один символ и помещает его в целую переменную c. Хотя из потока считываются символы, переменная c описана как целая, поскольку макрос getc() возвращает целое. Чтение символа часто включается в какую-либо управляющую конструкцию, например так:

       while ((c = getc (fin)) != EOF)
         . . .

Здесь символы будут считываться до тех пор, пока не будет достигнут конец файла. Константы EOF, NULL, а также макрос getc() определены в <stdio.h>. getc() и другие функции и макросы, составляющие пакет стандартного ввода/вывода, поддерживают продвижение указателя в буфере, ассоциированном с файлом. В случае достижения указателем конца буфера необходимая подкачка символов из файла в буфер (или запись символов из буфера в файл, в случае вывода) производятся функциями стандартного ввода/вывода и самой ОС UNIX. Эти действия системы не видны программе и программисту.

Для разрыва связи между дескриптора файла в Вашей программе и файлом, то есть для его закрытия, используется функция fclose(). После ее успешного выполнения дескриптор может быть закреплен за другим файлом с помощью следующего вызова функции fopen(). Такое переиспользование дескрипторов для различных потоков может быть необходимым, если Ваша программа открывает много файлов. Для выходных файлов рекомендуется вызывать fclose(), поскольку это гарантирует, что перед закрытием файла будет выведено все содержимое ассоциированного с ним буфера. Системный вызов exit() закрывает все открытые в программе файлы, однако этот вызов еще и полностью завершает выполнение программы, поэтому его использование безопасно, только если Вы уверены, что сделано все, что нужно.

4.6.3. Низкоуровневый ввод/вывод и почему не стоит им пользоваться

Под низкоуровневым понимается ввод/вывод с применением системных вызовов, описанных в разделе 2 Справочника программиста, а не функций и макросов из стандартного пакета ввода/вывода. Даже если кажется, что эти системные вызовы очень подходят для решения Ваших проблем, без них, скорее всего, можно обойтись. Можно много лет программировать на C в ОС UNIX, не используя системные вызовы для организации ввода/вывода и доступа к файлам. Использование этих системных вызовов не рекомендуется, поскольку их реализация более системно-зависима, чем соответствующих функций из пакета стандартного ввода/вывода. В результате программы будут менее мобильными и, видимо, не более эффективными.

4.7. Управление окружением и получение информации о его состоянии

В некоторых обстоятельствах может потребоваться информация об окружении на компьютере, а также возможность управления окружением. Для этого имеется набор системных вызовов. Некоторые из них приведены в следующей таблице:

Имена функций Назначение
chdir  Изменение текущего каталога.
chmod  Изменение режима доступа к файлу.
chown  Изменение владельца и группы файла.
getpid getpgrp getppid  Получение идентификаторов процесса.
getuid geteuid getgid  Получение идентификаторов пользователя.
ioctl  Управление устройствами.
link unlink  Создание или удаление ссылки на файл.
mount umount  Монтирование/размонтирование файловой системы.
nice  Изменение приоритета процесса.
stat fstat  Получение статуса файла.
time  Получение системного времени.
ulimit  Получение или изменение ограничений процесса.
uname  Получение имени текущей UNIX-системы.

Как можно заметить, многие из приведенных в таблице функций эквивалентны соответствующим командам shell'а. Действительно, необходимые действия по управлению окружением можно выполнить с помощью shell'а. Тем не менее, упомянутые функции могут использоваться в C-программах как часть интерфейса между ОС UNIX и языком C. Описание этих функций содержится в разделе 2 Справочника программиста.

4.8. Процессы

В ОС UNIX всякий раз, когда Вы выполняете команду, запускается процесс, идентифицируемый и отслеживаемый операционной системой. Характерной особенностью ОС UNIX является то, что одни процессы могут порождаться другими. Это происходит чаще, чем может показаться. Например, когда Вы входите в систему, запускается процесс, скорее всего это shell. Если затем войти в редактор РК, запустить из него shell и выполнить команду ps -f, на экране появится примерно следующее:

    UID   PID  PPID  C    STIME TTY     TIME COMMAND
userow     94     1  0 10:15:56 tty6    0:02 -sh
userow    116    94  0 10:16:25 tty6    0:00 -sh
userow    125   116  0 10:16:27 tty6    9:38 /dss/rk/rk.20.02
-hrk=/dss/rk/d.hrk -nocore -in=stdin -out=stdout -display=D211
userow    285   125  1 13:11:10 tty6    0:00 sh
userow    290   285 10 13:11:58 tty6    0:00 ps -f

Таким образом, у пользователя userow, проделавшего описанные выше действия, имеется 5 активных процессов. Интересно проследить переключения в колонках идентификатор процесса (PID) и идентификатор родительского процесса (PPID). Shell, запущенный при входе пользователя userow в систему, имеет идентификатор процесса 94; его предком является процесс инициализации (его идентификатор равен 1). Процесс с идентификатором 116 порожден для выполнения shell-процедуры rk; он является предком процесса с идентификатором 125 и т.д.

Точно так же новые процессы порождаются при выполнении Ваших собственных программ. Действительно, ввод команды запуска Вашей программы означает запрос к shell'у на порождение еще одного процесса, построенного из Вашего выполняемого файла со всеми функциями, помещенными в него редактором связей.

Можно рассуждать следующим образом: "Хорошо, когда я работаю с компьютером в интерактивном режиме, удобно иметь возможность запускать поочередно различные программы. Но зачем нужно, чтобы из одной программы можно было запускать другую, почему бы не создать один большой выполняемый файл, объединяющий все необходимые программы?"

Если Ваша программа сама является интерактивной и предоставляет пользователю широкий выбор функций, необходимо иметь возможность запускать из нее другие программы, в зависимости от удовлетворения при ее выполнении тех или иных условий. (Например, если конец месяца, то приготовить отчет). Типичные причины, по которым не принято создавать один большой выполняемый файл, состоят в следующем:

Таким образом, имеется достаточно аргументов в пользу необходимости создания новых процессов. Для создания процессов есть три основных средства:

4.8.1. system(3S)

Формальное описание функции system() выглядит так:
       #include <stdio.h>
       
       int system (string)
       char *string;

Здесь аргумент string трактуется shell'ом как командная строка. Таким образом, string может содержать имя и аргументы любой выполняемой программы или стандартной команды ОС UNIX. Если аргументы передаваемой команды заранее не известны, то для формирования нужного значения string можно воспользоваться функцией sprintf(). Возвращаемое функцией system() значение является кодом завершения shell'а. При обращении к system() вызвавшая программа ожидает завершения выполнения переданной команды, а затем продолжает выполнение со следующего выполняемого оператора.

4.8.2. exec(2)

exec() - это наименование целого семейства функций: execv(), execl(), execle(), execve(), execlp() и execvp(). Все они превращают вызвавший процесс в другой. Отличия между функциями заключаются в способе представления аргументов. Например, функция execl() может быть вызвана следующим образом:

       execl ("/bin/prog", "prog", arg1, arg2, (char*) 0);
Аргументы функции execl() имеют следующий смысл:
    /bin/prog  Маршрутное имя нового выполняемого файла.
    prog  Имя, которое новый процесс получит как argv[0].
    arg1, ...  Указатели на цепочки символов, содержащие аргументы программы prog.
    (char *) 0  Пустая ссылка, отмечающая конец списка аргументов.

Более подробная информация об этих функциях содержится в Справочнике программиста. Ключевым свойством функций семейства exec является то, что в случае их успешного завершения управление не возвращается, поскольку вызвавший процесс заменен новым. При этом новый процесс наследует у старого его идентификатор и другие характеристики. Если обращение к exec() завершается неудачей, управление возвращается в вызвавшую программу с результатом -1. Причина неудачи может быть установлена по значению переменной errno (см. ниже).

4.8.3. fork(2)

Системный вызов fork() создает новый процесс - точную копию вызвавшего процесса. В этом случае новый процесс называется порожденным процессом, а вызвавший процесс - родительским. Единственное существенное отличие между этими двумя процессами заключается в том, что порожденный процесс имеет свой уникальный идентификатор. В случае успешного завершения fork() возвращает порожденному процессу 0, а родительскому процессу - идентификатор порожденного процесса. Идея получения двух одинаковых процессов может показаться несколько странной, однако:

Чтобы переложить приведенные рассуждения на язык C, можно включить в программу операторы, аналогичные следующим:

       #include <errno.h>
       
       int ch_stat, ch_pid, status;
       char *progarg1;
       char *progarg2;
       void exit ();
       extern int errno;

         if ((ch_pid = fork ()) < 0 ) {
           /* Вызов fork завершился неудачей ...
              проверка переменной errno */
         }
         else if (ch_pid == 0) {
          /* Порожденный процесс */
          (void) execl ("/bin/prog","prog",arg1,arg2,(char*)0);
          exit (2);   /* execl() завершился неудачей */
         }
         else {
           /* Родительский процесс */
           while ((status = wait (&ch_stat)) != ch_pid) {
             if (status < 0 && errno == ECHILD)
               break;
             errno = 0;
           }
         }

Поскольку при вызове exec() идентификатор порожденного процесса наследуется новым процессом, родительский процесс знает этот идентификатор. Действие приведенного фрагмента программы сводится к остановке выполнения одной программы и запуску другой, с последующим возвращением в ту точку первой программы, где было остановлено ее выполнение. В точности то же самое происходит при вызове функции system(3S). Действительно, реализация system() такова, что при ее выполнении вызываются fork(), exec() и wait().

Следует иметь в виду, что в данный пример включено минимальное количество проверок различных ошибок. Так, необходимо проявлять осторожность при совместной работе с файлами. Помимо именованных файлов, новый процесс, порожденный вызовом fork() или exec(), наследует три открытых файла: stdin, stdout и stderr. Если вывод родительского процесса буферизован и должен появиться до того, как порожденный процесс начнет свой вывод, буфера должны быть вытолкнуты до вызова fork(). А если родительский и порожденный процессы читают из некоторого потока, то прочитанное одним процессом уже не может быть прочитано другим, поскольку при чтении продвигается указатель текущей позиции в файле.

4.8.4. Каналы

В окружении ОС UNIX при работе в рамках shell'а часто используются каналы, то есть выполнение программ огранизуется таким образом, что вывод одной программы является вводом другой. Например, чтобы узнать количество архивных файлов на Вашем компьюте- ре, можно ввести следующую команду:

       echo /lib/*.a /usr/lib/*.a | wc -w

Эта команда выводит имена всех файлов из каталогов /lib и /usr/lib, оканчивающиеся на .a, и направляет результат команде wc(1), которая подсчитывает количество файлов.

Особенностью интерфейса между ОС UNIX и языком C является возможность создания каналов между Вашим процессом и выполняемой shell'ом командой, или между двумя взаимодействующими процесссами. В первом случае используется функция popen(3S), входящая в стандартный пакет ввода/вывода, а во втором - системный вызов pipe(2).

Функция popen() напоминает функцию system() тем, что она вызывает выполнение указанной команды shell'а. Отличие заключается в том, что при использовании функции popen() между вызвавшей ее программой и командой создается канал. С помощью функций пакета стандартного ввода/вывода можно выводить символы и цепочки символов в этот канал точно так же, как в stdout или именованные файлы. Канал остается открытым до тех пор, пока не будет вызвана функция pclose(). Типичным применением popen() является организация канала для выдачи информации на устройство печати командой lp(1):

       #include <stdio.h>
       
       main ()
       {
         FILE *pptr;
         char *outstring;

         if ((pptr = popen ("lp", "w")) != NULL) {
           for (;;) {
               . . .
             /* Организация вывода */
               . . .
             (void) fprintf (pptr, "%s\n", outstring);
               . . .
           }
             . . .
         pclose (pptr);
         }
           . . .
       }

4.9. Обработка ошибок

Внутри C-программ необходимо проверять с требуемой строгостью правильность данных, а также допустимость значений, возвращаемых функциями. Для системных вызовов, описанных в разделе 2 Справочника программиста, имеется стандартный способ определения вероятной причины неудачного завершения.

В случае неудачного завершения почти всегда системные вызовы ОС UNIX возвращают вызвавшей программе значение -1. (Если Вы просмотрите описание системных вызовов в разделе 2, Вы увидите, что имеется все же несколько вызовов, для которых возвращаемое значение не определено, но это исключения.) При неудачном завершении, кроме возврата значения -1, системные вызовы помещают код ошибки во внешнюю переменную errno. Чтобы переменная errno была доступна Вашей программе, необходимо включить в программу оператор

       #include <errno.h>

При успешном завершении системного вызова значение переменной errno не изменяется, поэтому оно имеет смысл только в случае, когда какой-либо системный вызов вернул -1. Список кодов ошибок приведен в Справочнике программиста в статье intro(2).

Для того, чтобы по коду ошибки, помещенному в errno, вывести в стандартный протокол сообщение об ошибке, можно воспользоваться функцией perror(3C).

4.10. Сигналы и прерывания

Термины сигнал и прерывание в контексте ОС UNIX являются синонимами: оба означают сообщения, посылаемые операционной системой выполняющимся процессам. Как правило, результатом получения сигнала является прекращение выполнения процесса. Некоторые сигналы генерируются, если процесс пытается выполнить недопустимые действия; другие сигналы могут инициироваться обычным пользователем для своих собственных процессов, или суперпользователем для всех процессов.

Чтобы послать сигнал из одного процесса другому, также имеющему Ваш идентификатор пользователя, можно воспользоваться системным вызовом kill(2). Он имеет следующий формат:

       int kill (pid, sig)
       int pid, sig;
где pid - это идентификатор процесса, которому посылается сигнал, а sig - целое число от 1 до 19, обозначающее посылаемый сигнал. Название kill является некоторым преувеличением - далеко не все сигналы смертельны. Ниже приведены некоторые сигналы, определенные во включаемом файле <sys/signal.h>.
       #define SIGHUP  1  /* Освобождение линии */
       #define SIGINT  2  /* Прерывание */
       #define SIGQUIT 3  /* Выход */
       #define SIGILL  4  /* Некорректная команда. Не пере-
           устанавливается при перехвате */
       #define SIGTRAP 5  /* Трассировочное прерывание. Не пере-
           устанавливается при перехвате */
       #define SIGIOT  6  /* Машинная команда IOT */
       #define SIGABRT 6  /* Рекомендуемый синоним предыдущего */
       #define SIGEMT  7  /* Машинная команда EMT */
       #define SIGFPE  8  /* Исключительная ситуация при выполнении
           операции с вещественными числами */
       #define SIGKILL 9  /* Уничтожение процесса. Не перехватывается
           и не игнорируется */
       #define SIGBUS  10 /* Ошибка шины */
       #define SIGSEGV 11 /* Некорректное обращение к сегменту
           памяти */
       #define SIGSYS  12 /* Некорректный параметр системного
           вызова */
       #define SIGPIPE 13 /* Запись в канал, из которого некому
           читать */
       #define SIGALRM 14 /* Будильник */
       #define SIGTERM 15 /* Программный сигнал завершения */
       #define SIGUSR1 16 /* Определяемый пользователем сигнал 1 */
       #define SIGUSR2 17 /* Определяемый пользователем сигнал 2 */
       #define SIGCLD  18 /* Завершение порожденного процесса */
       #define SIGPWR  19 /* Ошибка питания */

       /* Сигналы SIGWIND и SIGPHONE используются только в UNIX/PC */
       /*#define SIGWIND 20 */ /* Изменение окна */
       /*#define SIGPHONE 21*/ /* Изменение строки состояния */

       #define SIGPOLL 22 /* Регистрация выборочного события */

       #define NSIG    23 /* Максимально допустимый номер сигнала.
           Сигналы могут иметь номера от 1 до
           NSIG-1 */
       #define MAXSIG  32 /* Размер u_signal[], NSIG-1<=MAXSIG.
           MAXSIG больше, чем сейчас необходимо.
           В будущем, возможно, будут добавлены
           новые сигналы, при этом не придется
           менять user.h */

С помощью системного вызова signal(2) можно выбрать один из трех возможных способов реакции на получаемые сигналы. Имеется возможность:

 

5. АНАЛИЗ И ОТЛАДКА ПРОГРАММ

В ОС UNIX имеется несколько команд, помогающих обнаруживать сделанные ошибки или предупреждающих о возможных неприятностях.

5.1. Пример программы

Чтобы показать, как работают различные отладочные команды, и проанализировать их выдачу, мы написали программу, которая открывает и читает входной файл и выполняет от одной до трех функций, в зависимости от указанных в командной строке опций. Все, что делает эта программа, можно проделать и с помощью карманного калькулятора; она предназначена лишь для демонстрации отладочных средств.

Приводимая в данном разделе выдача различных отладочных средств может несколько отличаться от выдачи Вашего компьютера. Для получения более подробной информации об этих командах лучше всего обратиться к Справочнику пользователя.

Сначала приведем содержимое файла restate.c, содержащего исходный текст главной программы.

               /* Файл с главной программой -- restate.c */
       
       #include <stdio.h>
       #include "recdef.h"
       
       #define TRUE    1
       #define FALSE   0
       
       main (argc, argv)
         int argc;
         char *argv [];
       {
         FILE *fopen (), *fin;
         void exit ();
         int getopt ();
         int oflag = FALSE;
         int pflag = FALSE;
         int rflag = FALSE;
         int ch;
         struct rec first;
         extern int opterr;
         extern float oppty (), pft (), rfe ();

         if (argc < 2) {
           (void) fprintf (stderr, "%s: Не указана опция\n",
             argv [0]);
           (void) fprintf (stderr, "Использование: %s -rpo\n",
             argv [0]);
           exit (2);
         }

         opterr = FALSE;

         while ((ch = getopt (argc, argv, "opr")) != EOF) {
           switch (ch) {
             case 'o':
               oflag = TRUE;
               break;
             case 'p':
               pflag = TRUE;
               break;
             case 'r':
               rflag = TRUE;
               break;
             default:
               (void) fprintf (stderr,"Использование: %s -opr\n",
        argv [0]);
               exit (2);
           }
         }

         if ((fin = fopen ("info", "r")) == NULL) {
           (void) fprintf (stderr,
             "%s: Неудача при открытии файла %s\n",
             argv [0], "info");
           exit (2);
         }

         if (fscanf (fin, "%s%f%f%f%f%f%f", first.pname,
               &first.ppx, &first.dp, &first.i, &first.c,
               &first.t, &first.spx) != 7) {
           (void) fprintf (stderr,
             "%s: Неудача при чтении первой записи из %s\n",
             argv [0], "info");
           exit (2);
         }

         printf ("Наименование: %s\n", first.pname);

         if (oflag)
           printf ("Приемлемая цена: $%#5.2f\n", oppty (&first));

         if (pflag)
           printf ("Ожидаемая прибыль (потери): $%#7.2f\n",
             pft (&first));

         if (rflag)
           printf("Фондоотдача: %#3.2f%%\n", rfe (&first));
       }

Файл oppty.c содержит исходный текст одноименной функции.

               /* Приемлемая цена -- oppty.c */
       
       #include "recdef.h"
       
       float oppty (ps)
         struct rec *ps;
       {
         return (ps->i/12 * ps->t * ps->dp);
       }

В файле pft.c описана функция для вычисления прибыли.

               /* Прибыль -- pft.c */
       
       #include "recdef.h"
       
       float pft (ps)
         struct rec *ps;
       {
         return (ps->spx - ps->ppx + ps->c);
       }

В файле rfe.c описана функция для вычисления фондоотдачи.

               /* Фондоотдача -- rfe.c */
       
       #include "recdef.h"
       
       float rfe (ps)
         struct rec *ps;
       {
         return (100 * (ps->spx - ps->c) / ps->spx);
       }

Наконец, приведем содержимое включаемого файла recdef.h.

               /* Включаемый файл -- recdef.h */
       
       struct rec {  /* Структура для хранения исходных данных */
         char pname [25];
         float ppx;
         float dp;
         float i;
         float c;
         float t;
         float spx;
       };

5.2. cflow(1)

Команда cflow(1) выдает граф внешних ссылок для C-, YACC-, LEX-, а также ассемблерных и объектных файлов. Используя файлы нашего примера, с помощью команды

       cflow restate.c oppty.c pft.c rfe.c

можно получить следующий результат:

       1      main: int(), <restate.c 12>
       2      fprintf: <>
       3      exit: <>
       4      getopt: <>
       5      fopen: <>
       6      fscanf: <>
       7      printf: <>
       8      oppty: float(), <oppty.c 7>
       9      pft: float(), <pft.c 7>
       10     rfe: float(), <rfe.c 7>

Та же команда с опцией -r заменяет отношение "вызывающий-вызываемый" на обратное. Результат выполнения команды:

       1       exit: <>
       2      main : <>
       3       fopen: <>
       4      main : 2
       5       fprintf: <>
       6      main : 2
       7       fscanf: <>
       8      main : 2
       9       getopt: <>
       10     main : 2
       11      main: int(), <restate.c 12>
       12      oppty: float(), <oppty.c 7>
       13     main : 2
       14      pft: float(), <pft.c 7>
       15     main : 2
       16      printf: <>
       17     main : 2
       18      rfe: float(), <rfe.c 7>
       19     main : 2

Команда cflow с опцией -ix включает в результат ссылки на внешние и статические данные. В нашем примере есть только одна такая ссылка - opterr. Результат:

       1      main: int(), <restate.c 12>
       2      fprintf: <>
       3      exit: <>
       4      opterr: <>
       5      getopt: <>
       6      fopen: <>
       7      fscanf: <>
       8      printf: <>
       9      oppty: float(), <oppty.c 7>
       10     pft: float(), <pft.c 7>
       11     rfe: float(), <rfe.c 7>

Если указать и опцию -r, и опцию -ix, результат будет следующим:

       1       exit: <>
       2      main : <>
       3       fopen: <>
       4      main : 2
       5       fprintf: <>
       6      main : 2
       7       fscanf: <>
       8      main : 2
       9       getopt: <>
       10     main : 2
       11      main: int(), <restate.c 12>
       12      oppty: float(), <oppty.c 7>
       13     main : 2
       14      opterr: <>
       15     main : 2
       16      pft: float(), <pft.c 7>
       17     main : 2
       18      printf: <>
       19     main : 2
       20      rfe: float(), <rfe.c 7>
       21     main : 2

5.3. ctrace(1)

Используя команду ctrace(1), можно проследить выполнение C-программы по операторам. ctrace читает исходный текст программы из файла и добавляет в него операторы печати для вывода значений переменных после каждого выполненного оператора. Результат выполнения команды следует направить во временный .c-файл, который затем использовать как входной для команды cc. Во время выполнения полученного в разультате файла a.out будет генерироваться вывод, в котором содержится много полезной информации о событиях в Вашей программе.

С помощью опций команды ctrace можно ограничить количество итераций при выполнении циклов. В исходный текст программы можно вставить функции, включающие и выключающие трассировку. Таким образом можно организовать трассировку только необходимых участков программы.

При обращении к ctrace можно указать только один файл с C-программой. Поэтому, чтобы проиллюстрировать действие ctrace на нашем примере, необходимо выполнить следующие команды:

       ctrace restate.c > ct1.c
       ctrace oppty.c > ct2.c
       ctrace pft.c > ct3.c
       ctrace rfe.c > ct4.c

Здесь имена выходных файлов выбраны совершенно произвольно. Можно использовать любые подходящие имена. Выбранные имена файлов должны оканчиваться на .c, поскольку эти файлы будут использоваться как входные для системы компиляции языка C:

       cc -o ctt ct1.c ct2.c ct3.c ct4.c
Затем команда
       ctt -opr

выдаст приведенный ниже результат на стандартный вывод (stdout). (Напомним, что программа читает исходные данные из файла info.) Разумеется, этот результат можно направить в какой-нибудь файл или вывести на печать для последующей обработки или изучения.

        9 main (argc, argv)
       24   if (argc < 2)
            /* argc == 2 */
       32   opterr = FALSE;
            /* FALSE == 0 */
            /* opterr == 0 */
       34   while ((ch = getopt (argc, argv, "opr")) != EOF)
            /* argc == 2 */
            /* argv == 2147483384 */
            /* ch == 111 or 'o' */   {
       35     switch (ch)
              /* ch == 111 or 'o' */
       36       case 'o':
       37     oflag = TRUE;
              /* TRUE == 1 */
              /* oflag == 1 */
       38     break;
       50   }

       34   while ((ch = getopt (argc, argv, "opr")) != EOF)
            /* argc == 2 */
            /* argv == 2147483384 */
            /* ch == 112 or 'p' */   {
       35     switch (ch)
              /* ch == 112 or 'p' */
       39       case 'p':
       40     pflag = TRUE;
              /* TRUE == 1 */
              /* pflag == 1 */
       41     break;
       50   }

       34   while ((ch = getopt (argc, argv, "opr")) != EOF)
            /* argc == 2 */
            /* argv == 2147483384 */
            /* ch == 114 or 'r' */   {
       35     switch (ch)
              /* ch == 114 or 'r' */
       42       case 'r':
       43     rflag = TRUE;
              /* TRUE == 1 */
              /* rflag == 1 */
       44     break;
       50   }

       34   while ((ch = getopt (argc, argv, "opr")) != EOF)
            /* argc == 2 */
            /* argv == 2147483384 */
            /* ch == -1 */
       52   if ((fin = fopen ("info", "r")) == NULL)
            /* fin == 1052602 */
       59   if (fscanf (fin, "%s%f%f%f%f%f%f", first.pname,
              &first.ppx, &first.dp, &first.i, &first.c,
              &first.t, &first.spx) != 7)
            /* fin == 1052602 */
            /* first.pname == 2147483294 */
       68   printf ("Наименование: %s\n", first.pname);
            /* first.pname == 2147483294 or "Tutti_Frutti" */
       Наименование: Tutti_Frutti

       70   if (oflag)
            /* oflag == 1 */
       71     printf ("Приемлемая цена: $%#5.2f\n", oppty (&first));
        5 oppty (ps)
        8   return (ps->i/12 * ps->t * ps->dp);
            /* ps->i == 1074108825 */
            /* ps->t == 1073741824 */
            /* ps->dp == 1074339512 */ Приемлемая цена: $4321.00

       73   if (pflag)
            /* pflag == 1 */
       74     printf ("Ожидаемая прибыль (потери): $%#7.2f\n",
            pft (&first));
        5 pft (ps)
        8   return (ps->spx - ps->ppx + ps->c);
            /* ps->spx == 1076101120 */
            /* ps->ppx == 1072693248 */
            /* ps->c == 1073259479 */ Ожидаемая прибыль (потери):
       $12345.00

       77   if (rflag)
            /* rflag == 1 */
       78     printf("Фондоотдача: %#3.2f%%\n", rfe (&first));
        5 rfe (ps)
        8   return (100 * (ps->spx - ps->c) / ps->spx);
            /* ps->spx == 1076101120 */
            /* ps->c == 1073259479 */ Фондоотдача: 95.00%

          /* return */

Используя в качестве примера правильно работающую программу, трудно продемонстрировать возможности ctrace. Интереснее было бы обнаружить с помощью ctrace ошибку. По-видимому, эта утилита наиболее полезна в случае, когда программа выполняется до конца, но ее результат не совпадает с ожидаемым.

5.4. cxref(1)

Команда cxref(1) анализирует группу .c-файлов и строит для каждого файла таблицу перекрестных ссылок на автоматические, статические и глобальные имена.

Ниже приводится результат выполнения команды

       cxref -c -o cx.op restate.c oppty.c pft.c rfe.c

Этот результат помещается в файл, в нашем примере это cx.op. Опция -c приводит к тому, что результат выполнения команды для четырех указанных файлов сливается в одну общую таблицу перекрестных ссылок.

       restate.c:
       
       oppty.c:
       
       pft.c:
       
       rfe.c:
       
            SYMBOL       FILE  FUNCTION   LINE
       
       BUFSIZ          /usr/include/stdio.h    --        *12
       EOF             /usr/include/stdio.h    --         43 *44
                       restate.c               --         34
       FALSE           restate.c               --        *7  16
        17  18  32
       FILE            /usr/include/stdio.h    --        *23
       L_ctermid       /usr/include/stdio.h    --        *79
       L_cuserid       /usr/include/stdio.h    --        *80
       L_tmpnam        /usr/include/stdio.h    --        *82
       NULL            /usr/include/stdio.h    --         40 *41
                       restate.c               --         52
       P_tmpdir        /usr/include/stdio.h    --        *81
       TRUE            restate.c               --        *6  37
        40  43

       _IOEOF          /usr/include/stdio.h    --        *35
       _IOERR          /usr/include/stdio.h    --        *36
       _IOFBF          /usr/include/stdio.h    --        *30
       _IOLBF          /usr/include/stdio.h    --        *37
       _IOMYBUF        /usr/include/stdio.h    --        *34
       _IONBF          /usr/include/stdio.h    --        *33
       _IOREAD         /usr/include/stdio.h    --        *31
       _IORW           /usr/include/stdio.h    --        *38
       _IOWRT          /usr/include/stdio.h    --        *32
       _NFILE          /usr/include/stdio.h    --         9 *10
        67

       _SBFSIZ         /usr/include/stdio.h    --        *15
       _base           /usr/include/stdio.h    --        *20
       _bufend()
                       /usr/include/stdio.h    --        *51
       _bufendtab      /usr/include/stdio.h    --        *77
       _bufsiz()
                       /usr/include/stdio.h    --        *52
       _cnt            /usr/include/stdio.h    --        *18
       _file           /usr/include/stdio.h    --        *22
       _flag           /usr/include/stdio.h    --        *21
       _iob            /usr/include/stdio.h    --        *67
                       restate.c               main       25  27
        46  53  62

       _ptr            /usr/include/stdio.h    --        *19
       argc            restate.c               --         9
                       restate.c               main      *10  24
        34
       argv            restate.c               --         9
                       restate.c               main      *11  26
        28  34  47  55  64

       c               ./recdef.h              --        *8
                       pft.c pft                          8
                       restate.c               main       60
                       rfe.c                   rfe        8
       ch              restate.c               main      *19  34
        35

       clearerr()
                       /usr/include/stdio.h    --        *61
       ctermid()
                       /usr/include/stdio.h    --        *71
       cuserid()
                       /usr/include/stdio.h    --        *71
       dp              ./recdef.h              --        *6
                       oppty.c                 oppty      8
                       restate.c               main       60
       exit()
                       restate.c               main      *14  29
        48  56  65

       fclose()
                       /usr/include/stdio.h    --        *72
       fdopen()
                       /usr/include/stdio.h    --        *68
       feof()
                       /usr/include/stdio.h    --        *62
       ferror()
                       /usr/include/stdio.h    --        *63
       fflush()
                       /usr/include/stdio.h    --        *72
       fgetc()
                       /usr/include/stdio.h    --        *72
       fgets()
                       /usr/include/stdio.h    --        *71
       fileno()
                       /usr/include/stdio.h    --        *64
       fin             restate.c               main      *13  52
        59

       first           restate.c               main      *20  59
        60  61  68  71  75  78

       fopen()
                       /usr/include/stdio.h    --        *68
                       restate.c               main       13  52
       fprintf()
                       /usr/include/stdio.h    --        *73
                       restate.c               main       25  27
        46  53  62

       fputc()
                       /usr/include/stdio.h    --        *74
       fputs()
                       /usr/include/stdio.h    --        *75
       fread()
                       /usr/include/stdio.h    --        *72
       freopen()
                       /usr/include/stdio.h    --        *68
       fscanf()
                       /usr/include/stdio.h    --        *75
                       restate.c               main       59
       fseek()
                       /usr/include/stdio.h    --        *72
       ftell()
                       /usr/include/stdio.h    --        *69
       fwrite()
                       /usr/include/stdio.h    --        *72
       getc()
                       /usr/include/stdio.h    --        *55
       getchar()
                       /usr/include/stdio.h    --        *59
       getopt()
                       restate.c               main      *15  34
       gets()
                       /usr/include/stdio.h    --        *71
       getw()
                       /usr/include/stdio.h    --        *73
       i               ./recdef.h              --        *7
                       oppty.c                 oppty      8
                       restate.c               main       60
       lint            /usr/include/stdio.h    --         54
       main()
                       restate.c               --        *9
       oflag           restate.c               main      *16  37
        70

       oppty()
                       oppty.c                 --        *5
                       restate.c               main      *22  71
       opterr          restate.c               main      *21  32
       p               /usr/include/stdio.h    --         51 *51
        52 *52  55 *55  56 *56  57  58  61 *61  62 *62  63 *63
        64 *64

       pclose()
                       /usr/include/stdio.h    --        *73
       pflag           restate.c               main      *17  40
        73

       pft()
                       pft.c                   --        *5
                       restate.c               main      *22  75
       pname           ./recdef.h              --        *4
                       restate.c               main       59  68
       popen()
                       /usr/include/stdio.h    --        *68
       ppx             ./recdef.h              --        *5
                       pft.c                   pft        8
                       restate.c               main       60
       printf()
                       /usr/include/stdio.h    --        *73
                       restate.c               main       68  71
        74  78

       ps              oppty.c                 --         5
                       oppty.c                 oppty     *6  8
                       pft.c                   --         5
                       pft.c                   pft       *6  8
                       rfe.c                   --         5
                       rfe.c                   rfe       *6  8
       putc()
                       /usr/include/stdio.h    --        *56
       putchar()
                       /usr/include/stdio.h    --        *60
       puts()
                       /usr/include/stdio.h    --        *75
       putw()
                       /usr/include/stdio.h    --        *74
       rec             ./recdef.h              --        *3
                       oppty.c                 oppty      6
                       pft.c                   pft        6
                       restate.c               main       20
                       rfe.c                   rfe        6
       rewind()
                       /usr/include/stdio.h    --        *70
       rfe()
                       restate.c               main      *22  78
                       rfe.c                   --        *5
       rflag           restate.c               main      *18  43
        77

       scanf()
                       /usr/include/stdio.h    --        *75
       setbuf()
                       /usr/include/stdio.h    --        *70
       setvbuf()
                       /usr/include/stdio.h    --        *76
       sprintf()
                       /usr/include/stdio.h    --        *73
       spx             ./recdef.h              --        *10
                       pft.c                   pft        8
                       restate.c               main       61
                       rfe.c                   rfe        8
       sscanf()
                       /usr/include/stdio.h    --        *75
       stderr          /usr/include/stdio.h    --        *49
                       restate.c               --         25  27
        46  53  62

       stdin           /usr/include/stdio.h    --        *47
       stdout          /usr/include/stdio.h    --        *48
       system()
                       /usr/include/stdio.h    --        *76
       t               ./recdef.h              --        *9
                       oppty.c                 oppty      8
                       restate.c               main       61
       tempnam()
                       /usr/include/stdio.h    --        *71
       tmpfile()
                       /usr/include/stdio.h    --        *68
       tmpnam()
                       /usr/include/stdio.h    --        *71
       ungetc()
                       /usr/include/stdio.h    --        *76
       vfprintf()
                       /usr/include/stdio.h    --        *74
       vprintf()
                       /usr/include/stdio.h    --        *74
       vsprintf()
                       /usr/include/stdio.h    --        *74
       x               /usr/include/stdio.h    --        *56  57
        58  60 *60

5.5. lint(1)

Утилита lint(1) обнаруживает в C-программах конструкции, которые могут привести к ошибкам во время выполнения, расточительно используют ресурсы или могут снизить мобильность программ.

Например, в результате выполнения команды

       lint restate.c oppty.c pft.c rfe.c

получаем:

       restate.c:
       
       restate.c
       ==============
       (79)  warning: main() returns random value to invocation
        environment
       oppty.c:
       pft.c:
       rfe.c:
       
       
       ==============
       function returns value which is always ignored
           printf

Для облегчения ориентации в тексте программ сообщения об ошибках содержат номера строк.

5.6. prof(1)

С помощью команды prof(1) можно выяснить, сколько времени было истрачено на выполнение различных фрагментов программы, а также сколько раз вызывалась та или иная функция. Чтобы использовать эту команду, необходимо откомпилировать файлы с опцией -p. После выполнения полученной программы будет сформирован файл mon.out, используемый командой prof для получения статистики. Кроме mon.out, используется файл a.out (или другой выполняемый файл).

Чтобы получить результаты профилирования для нашего примера, необходимо предпринять следующие шаги:

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

       %Time Seconds Cumsecs  #Calls   msec/call  Name
        50.0    0.02    0.02      14      1.2     ungetc
        50.0    0.02    0.03                      mcount%
         0.0    0.00    0.03       1      0.      rfe
         0.0    0.00    0.03       4      0.      getopt
         0.0    0.00    0.03       1      0.      pft
         0.0    0.00    0.03       1      0.      creat
         0.0    0.00    0.03       4      0.      printf
         0.0    0.00    0.03       2      0.      profil
         0.0    0.00    0.03       1      0.      fscanf
         0.0    0.00    0.03       1      0.      _doscan
         0.0    0.00    0.03       6      0.      atof
         0.0    0.00    0.03       1      0.      _filbuf
         0.0    0.00    0.03       3      0.      strchr
         0.0    0.00    0.03       1      0.      strcmp
         0.0    0.00    0.03       4      0.      ldexp
         0.0    0.00    0.03       4      0.      frexp
         0.0    0.00    0.03       1      0.      fopen
         0.0    0.00    0.03       1      0.      _findiop
         0.0    0.00    0.03       1      0.      oppty
         0.0    0.00    0.03       4      0.      _doprnt
         0.0    0.00    0.03       1      0.      main
         0.0    0.00    0.03       3      0.      fcvt
         0.0    0.00    0.03       3      0.      fwrite
         0.0    0.00    0.03       4      0.      _xflsbuf
         0.0    0.00    0.03       1      0.      _wrtchk
         0.0    0.00    0.03       2      0.      _findbuf
         0.0    0.00    0.03       4      0.      _bufsync
         0.0    0.00    0.03       3      0.      getenv
         0.0    0.00    0.03       2      0.      isatty
         0.0    0.00    0.03       2      0.      ioctl
         0.0    0.00    0.03       1      0.      malloc
         0.0    0.00    0.03       1      0.      open
         0.0    0.00    0.03       1      0.      read
         0.0    0.00    0.03       2      0.      sbrk
         0.0    0.00    0.03       1      0.      strcpy
         0.0    0.00    0.03       1      0.      strlen
         0.0    0.00    0.03       5      0.      write

5.7. size(1)

Команда size(1) выдает количество байт, занимаемое тремя секциями (.text, .data и .bss) объектного файла обычного формата при загрузке его в память для выполнения. Применяя эту команду к нашему объектному файлу, получаем:

       3876 + 1228 + 2240 = 7344

Не следует путать это число с количеством символов в объектном файле, полученное с помощью команды ls -l. В этом случае в результат будут входить также таблица имен и некоторая другая информация, не используемая при выполнении файла.

5.8. strip(1)

Команда strip(1) удаляет из объектного файла обычного формата таблицу имен и информацию о номерах строк. После применения команды strip количество символов в файле, выдаваемое командой ls -l, приближается к значению, получаемому по команде size. Но файл все еще содержит некоторые заголовки, которые не входят в секции .text, .data или .bss. После выполнения команды strip данный файл уже не допускает символьной отладки.

5.9. sdb(1)

sdb(1) - это символьный отладчик (Symbolic Debugger). Слово "символьный" означает, что во время отладки можно использовать символические имена из программы. С помощью sdb можно отлаживать программы на языках C и Фортран 77 [если использовался компилятор f77(1)]. sdb можно использовать двумя способами: либо для контролируемого выполнения программы, либо для анализа образа памяти, оставшегося после аварийного завершения программы. В первом случае можно проследить, что происходит при выполнении программы до того места, где она аварийно завершается (или обойти это место, с тем чтобы продолжить выполнение). Во втором случае можно анализировать состояние на момент аварийного завершения программы, что, возможно, позволит выяснить причину неправильного функционирования.

Отладчик sdb(1) подробно описан в Справочнике пользователя. Еще раз подчеркнем, что использование отладчика КРОТ предпочтительнее.

 

6. СРЕДСТВА ОРГАНИЗАЦИИ РАЗРАБОТКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

Следующие три утилиты предназначены для организации процесса разработки программного обеспечения.

6.1. Утилита make(1)

Если исходный текст программы размещается в нескольких файлах, при изменении одного из них необходимо выяснять, какие еще модули необходимо перекомпилировать. Используя утилиту make(1), можно записать все зависимости между файлами, гарантировав таким образом автоматическую перекомпиляцию всех модулей, зависимых от измененного. Даже управление такой простой программой, как рассматривавшаяся выше при обсуждении средств отладки, облегчается, если пользоваться утилитой make.

Для использования утилиты make необходимо с помощью редактора сформировать файл с описаниями. Этот файл (по умолчанию его имя суть makefile) содержит информацию, используемую командой make для построения целевого файла. Целевым файлом обычно является выполняемая программа. Файл описаний обычно содержит информацию трех типов:

Утилита make работает, проверяя время последнего изменения модулей, указанных в файле описаний. Если при этом оказывается, что у некоторого модуля время последнего изменения меньше, чем у модуля, от которого он зависит, то указанная команда (обычно компиляция) направляется shell'у для выполнения.

Команда make допускает три типа аргументов: опции, макроопределения и имена целевых файлов. Если в опции командной строки не задано имя файла с описаниями, то make отыскивает в текущем каталоге файлы с именами makefile или Makefile. Ниже приводится пример файла описаний.

       OBJECTS = restate.o oppty.o pft.o rfe.o
       all: restate
       restate: $(OBJECTS)
               $(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) -o restate
       
       $(OBJECTS): ./recdef.h
       
       clean:
               rm -f $(OBJECTS)
       
       clobber:   clean
               rm -f restate

В данном примере

Если Вы хотите тестировать или отлаживать результаты изменения одного из компонентов программы restate, для изменения опций команды cc можно, например, воспользоваться командой

       make CFLAGS=-g restate

Мы провели очень короткий обзор утилиты make; этой утилите посвящена отдельная глава Руководства.

6.2. Работа с архивами

Чаще всего архивные файлы используются для хранения объектных файлов, составляющих библиотеку. Имя библиотеки может указываться в командной строке при вызове редактора связей (или в опции редактора связей команды cc). В результате при разрешении ссылок редактор связей просматривает таблицу имен архивного файла.

Команда ar(1) служит для создания архивных файлов, различных действий над их содержимым, а также для операций над их таблицами имен. Синтаксис команды ar несколько отличается от принятого в ОС UNIX. В командной строке необходимо задать ключ - один из символов из набора drqtpmx. Значение ключа определяет действия команды ar. Для изменения способа выполнения требуемой операции ключ может быть скомбинирован с одним или несколькими символами из набора vuaibcls. Общий вид команды ar:

       ar  -ключ [позиционирующее_имя] а_файл [имя]...

где позиционирующее_имя - это имя элемента архива, которое используется с некоторыми ключевыми символами для управления размещением файлов в архиве; а_файл - это имя архивного файла. По соглашению, имена архивных файлов имеют расширение .a (например, libc.a - архивный файл, содержащий объектные файлы стандартных функций языка C). Наконец, может быть указано одно или несколько имен, задающих файлы, над которыми будет выполнена операция, определенная значением ключа.

С помощью следующей команды можно создать архивный файл, содержащий объектные модули, используемые в программе restate:

       ar -rv rste.a restate.o oppty.o pft.o rfe.o

Если в текущем каталоге нет других объектных файлов, то же можно проделать с помощью команды

       ar -rv rste.a *.o

В результате выполнения последней команды на экран будет выведено следующее:

       ar: creating rste.a
       a - oppty.o
       a - pft.o
       a - restate.o
       a - rfe.o

Команда nm(1) используется для получения различной информации из таблицы имен объектных файлов обычного формата. Объектные файлы могут, но не обязаны, храниться в архиве. Ниже приводится результат выполнения команды

       nm -f rste.a

Опция -f предписывает выдачу полной информации. Предполагается, что при компиляции использовалась опция -g.

       Symbols from rste.a[oppty.o]:
       
       Name       Value  Class    Type        Size Line Section
       
       oppty.c   |     | file |               |    |   |
       rec       |     |strtag|         struct|  50|   |
       pname     |    0|strmem|       char[25]|  25|   |(ABS)
       ppx       |   26|strmem|          float|    |   |(ABS)
       dp        |   30|strmem|          float|    |   |(ABS)
       i         |   34|strmem|          float|    |   |(ABS)
       c         |   38|strmem|          float|    |   |(ABS)
       t         |   42|strmem|          float|    |   |(ABS)
       spx       |   46|strmem|          float|    |   |(ABS)
       .eos      |     |endstr|               |  50|   |(ABS)
       oppty     |    0|extern|       float( )| 100|   |.text
       .bf       |    0|fcn   |               |    |  7|.text
       ps        |    8|argm't|    *struct-rec|  50|   |(ABS)
       .ef       |   80|fcn   |               |    |  3|.text
       .text     |    0|static|               |   4|   |.text
       .data     |  100|static|               |    |   |.data
       .bss      |  100|static|               |    |   |.bss

       Symbols from rste.a[pft.o]:
       
       Name       Value  Class    Type        Size Line Section
       
       pft.c     |     | file |               |    |   |
       rec       |     |strtag|         struct|  50|   |
       pname     |    0|strmem|       char[25]|  25|   |(ABS)
       ppx       |   26|strmem|          float|    |   |(ABS)
       dp        |   30|strmem|          float|    |   |(ABS)
       i         |   34|strmem|          float|    |   |(ABS)
       c         |   38|strmem|          float|    |   |(ABS)
       t         |   42|strmem|          float|    |   |(ABS)
       spx       |   46|strmem|          float|    |   |(ABS)
       .eos      |     |endstr|               |  50|   |(ABS)
       pft       |    0|extern|       float( )|  88|   |.text
       .bf       |    0|fcn   |               |    |  7|.text
       ps        |    8|argm't|    *struct-rec|  50|   |(ABS)
       .ef       |   68|fcn   |               |    |  3|.text
       .text     |    0|static|               |   4|   |.text
       .data     |   88|static|               |    |   |.data
       .bss      |   88|static|               |    |   |.bss

       Symbols from rste.a[restate.o]:
       
       Name       Value  Class    Type         Size Line Section
       
       restate.c |     | file |               |    |   |
       .0fake    |     |strtag|         struct|  14|   |
       _cnt      |    0|strmem|            int|    |   |(ABS)
       _ptr      |    4|strmem|         *Uchar|    |   |(ABS)
       _base     |    8|strmem|         *Uchar|    |   |(ABS)
       _flag     |   12|strmem|           char|    |   |(ABS)
       _file     |   13|strmem|           char|    |   |(ABS)
       .eos      |     |endstr|               |  14|   |(ABS)
       rec       |     |strtag|         struct|  50|   |
       pname     |    0|strmem|       char[25]|  25|   |(ABS)
       ppx       |   26|strmem|          float|    |   |(ABS)
       dp        |   30|strmem|          float|    |   |(ABS)
       i         |   34|strmem|          float|    |   |(ABS)
       c         |   38|strmem|          float|    |   |(ABS)
       t         |   42|strmem|          float|    |   |(ABS)
       spx       |   46|strmem|          float|    |   |(ABS)
       .eos      |     |endstr|               |  50|   |(ABS)
       main      |    0|extern|         int( )| 600|   |.text
       .bf       |    0|fcn   |               |    | 12|.text
       argc      |    8|argm't|            int|    |   |(ABS)
       argv      |   12|argm't|         **char|    |   |(ABS)
       fin       |   -4|auto  | *struct-.0fake|  14|   |(ABS)
       oflag     |   -8|auto  |            int|    |   |(ABS)
       pflag     |  -12|auto  |            int|    |   |(ABS)
       rflag     |  -16|auto  |            int|    |   |(ABS)
       ch        |  -20|auto  |            int|    |   |(ABS)
       first     |  -70|auto  |     struct-rec|  50|   |(ABS)
       .ef       |  580|fcn   |               |    | 68|.text
       FILE      |     |typdef|  struct-.0fake|  14|   |
       .text     |    0|static|               |  31| 40|.text
       .data     |  600|static|               |    |   |.data
       .bss      |  892|static|               |    |   |.bss
       _iob      |    0|extern|               |    |   |
       fprintf   |    0|extern|               |    |   |
       exit      |    0|extern|               |    |   |
       opterr    |    0|extern|               |    |   |
       getopt    |    0|extern|               |    |   |
       fopen     |    0|extern|               |    |   |
       fscanf    |    0|extern|               |    |   |
       printf    |    0|extern|               |    |   |
       oppty     |    0|extern|               |    |   |
       pft       |    0|extern|               |    |   |
       rfe       |    0|extern|               |    |   |

       Symbols from rste.a[rfe.o]:
       
       Name       Value  Class    Type         Size Line Section
       
       rfe.c     |     | file |               |    |   |
       rec       |     |strtag|         struct|  50|   |
       pname     |    0|strmem|       char[25]|  25|   |(ABS)
       ppx       |   26|strmem|          float|    |   |(ABS)
       dp        |   30|strmem|          float|    |   |(ABS)
       i         |   34|strmem|          float|    |   |(ABS)
       c         |   38|strmem|          float|    |   |(ABS)
       t         |   42|strmem|          float|    |   |(ABS)
       spx       |   46|strmem|          float|    |   |(ABS)
       .eos      |     |endstr|               |  50|   |(ABS)
       rfe       |    0|extern|       float( )| 104|   |.text
       .bf       |    0|fcn   |               |    |  7|.text
       ps        |    8|argm't|    *struct-rec|  50|   |(ABS)
       .ef       |   84|fcn   |               |    |  3|.text
       .text     |    0|static|               |   4|   |.text
       .data     |  104|static|               |    |   |.data
       .bss      |  104|static|               |    |   |.bss

Для работы команды nm требуется, чтобы все содержимое архива было составлено из объектных модулей. Если поместить в архив что-либо другое, то при выполнении команды nm будет выдано сообщение:

       nm:  rste.a:  bad magic

6.3. Использование системы SCCS программистами-одиночками

SCCS (Source Code Control System) - это набор утилит, предназначенных для управления версиями исходных текстов программ. Если версии программы отслеживаются с помощью SCCS, то в любой момент времени возможно редактирование только одной версии. Когда измененный текст программы возвращается SCCS, записываются только сделанные изменения. Каждая версия имеет свой с_идентификатор. Указывая с_идентификатор, можно выделить из SCCS-файла более раннюю версию текста программы. Если отредактировать и возвратить SCCS для сохранения такую раннюю версию, в дереве версий будет начата новая ветвь. В состав SCCS входят утилиты admin(1), cdc(1), comb(1), delta(1), get(1), prs(1), rmdel(1), sccsdiff(1), val(1), what(1). Все они описаны в Справочнике пользователя.

Как правило, считается, что SCCS применяется для управления большими программными проектами. Тем не менее любой пользователь вполне может организовать SCCS-систему для личных нужд.


НазадОглавлениеВперед
КаталогИндекс раздела