Linux. Полное руководство.
Глава 21. Создаем консольное приложение.
21.1. Компилятор gcc.
В 8 главе вы познакомились с языком командного интерпретатора и убедились, что на нем можно писать полезные сценарии. Но если вы раньше программировали под Windows, то интерпретатора вам маловато будет — понадобятся более мощные средства разработки. Разумеется, они в ОС Linux есть.
Основным C-компилятором в Linux служит gcc (GNU С Compiler). Если вы не установили средства разработки при установке дистрибутива, самое время сделать это сейчас. Запустите менеджер пакетов (рис. 21.1) и установите следующие пакеты:
♦ gcc — сам компилятор gcc;
♦ gcc-c++ — поддержка С++;
♦ gcc-doc — документация по gcc (очень рекомендую установить);
♦ compat-gcc — пакет, содержащий средства gcc для обратной совместимости. Данный пакет вам нужен, если вы планируете писать программы для более старых систем, чем ваша;
♦ compat-gcc-c++ — то же, что и compat-gcc, только с поддержкой С++;
♦ compat-cpp — препроцессор cpp для обратной совместимости;
♦ binutils — набор вспомогательных утилит, о которых мы поговорим в последнем пункте этой главы;
♦ glibc-devel — содержит библиотеки для разработки C-программ;
♦ libbfd — библиотека дескриптора двоичного файла (Binary File Descriptor library);
♦ libstdc++-devel — заголовочные файлы и библиотеки для программирования на С++;
♦ make — утилита, упрощающая процесс сборки больших проектов.
Рис. 21.1. Менеджер пакетов Fedora Core — system-config-packages.
21.1.1. Вызов gcc.
Формат вызова компилятора такой:
Gcc [опции] helloworld.с.
Вы написали свою программу на С++? Нет проблем: компиляторы С и С++ являются интегрированными. Чтобы сообщить компилятору, на каком языке вы писали свою программу, нужно указать определенную опцию при вызове компилятора. Но можно поступить проще: по умолчанию компилятор считает, что файлы с расширением .с написаны на языке С, файлы с одним из расширений .C, .cc, .cXX — на языке С++, а файлы с расширением .s — на языке ассемблера.
Как правило, перед компиляцией вызывается программа cpp — препроцессор языка С. Препроцессор создаст файл с расширением .i, если ваша программа написана на языке С. и файл с расширением .ii, если ваша программа написана на С++. Если ваша программа уже прошла препроцессорную подготовку, вы можете передать компилятору gcc файл с расширением .i или .ii и gcc уже не будет вызывать препроцессор для подготовки исходного текста. Как правило, программу cpp редко кто вызывает вручную.
Если не указывать никаких опций, то компилятор создаст в текущем каталоге файл a.out, содержащий исполняемый код. Для тех, кто изучал другой язык, я на всякий случай приведу текст первой программы на С (листинг 21.1).
Листинг 21.1. Файл helloworld.c.
#include <stdio.h>
Main() {
printf("Hello World!\n");
}
А сейчас рассмотрим основные опции компилятора. Обо всех остальных опциях вы сможете узнать по команде man gcc.
21.1.2. Общие опции.
Из общих опций наиболее интересны три: -х, -с и -о.
С помощью опции -х можно указать язык, на котором написан исходный код, например,
Gcc -х с helloworld.c.
В качестве языка программы вы можете указать:
♦ с, если ваша программа написана на С;
♦ objective_c, если ваша программа написана на Objective С;
♦ c-header, если ваша программа является заголовком С;
♦ C++, если вы написали программу на С++;
♦ assembler, если вы написали программу на ассемблере.
Существуют и другие варианты, но они не столь важны.
Опция -c используется, если вы хотите только откомпилировать вашу программу, но не вызвать компоновщик. В результате будет создан объектный файл с расширением .о.
Опция -о позволяет указать имя результирующего файла. Это очень полезная опция, потому что имя a.out мало кого устраивает:
Gcc -о helloworld helloworld.c.
Очень полезна опция -v, которая выводит различную информацию о стадиях компиляции. Кроме того, эта опция выводит версию компилятора.
21.1.3. Опции языка.
Из всех опций языка мне пригодилась лишь опция ANSI, которая выключает все функции GNU С, несовместимые со стандартом ANSI. К таким функциям относятся asm, inline, typeof и другие.
21.1.4. Опции препроцессора.
Эти опции задают режим предварительной обработки исходного кода (до собственно компиляции).
Очень важной является опция -include <файл>. Она позволяет «прогнать» через препроцессор сперва содержимое указанного файла, а только после этого файл, который передан препроцессору. В результате указанный файл будет откомпилирован раньше, чем все остальные.
Опция -nostdinc запрещает использование системного каталога, содержащего файлы заголовков. При ее включении компилятор будет искать заголовки в каталогах, указанных в опции -I и в текущем каталоге.
Опция -nostdinc++ запрещает использование стандартных файлов заголовков для языка С++.
21.1.5. Опции компоновщика.
Опция компоновщика -l позволяет явно указать имя библиотеки, которая будет использоваться при сборке вашей программы. Например,
Gcc -lmylibrary myfile.c.
Компоновщик будет использовать файл libmylibrary.a, который он попытается найти в системных каталогах библиотек и каталогах, которые вы укажете с помощью опции -L.
Опция -nostdlib запрещает использовать все системные библиотеки. При этом будут использованы библиотеки только из тех каталогов, которые вы укажете с помощью опции -L.
Опция -static означает, что будет использована статическая линковка.
21.1.6. Опции каталогов.
Две важнейшие опции каталогов: -I и -L. Первая позволяет указать путь для поиска заголовков (файлов с расширением .h), а вторая — библиотек. Например, если вы хотите, чтобы компилятор использовал файлы заголовков, которые находятся в каталоге /root/include, тогда укажите опцию -I/root/include. Обратите внимание на отсутствие пробела между буквой I и первым символом пути.
Если вы укажете опцию -I-, то в каталогах, которые вы укажете в объявленной до этого опции -I, будет производиться поиск только пользовательских заголовков, то есть заголовков, указанных в директиве #include "файл". Пути для поиска системных заголовков, которые указываются директивой #include <файл>, останутся неизменными.
21.1.7. Опции отладки.
Если вы хотите использовать отладчик, например gdb, для отладки своей программы, укажите опцию -g при вызове компилятора. Эта опция помещает в откомпилированный файл отладочную информацию, вследствие чего существенно увеличивается объем файла. Поэтому никогда не используйте эту опцию для окончательной версии продукта.
21.1.8. Опции оптимизации.
Компилятор gcc позволяет оптимизировать код вашей программы. Другими словами, gcc сделает все для того, чтобы ваша программа была как можно меньше по размеру и как можно быстрее запускалась. Для включения режима оптимизации используйте опцию -O1. Вы можете поэкспериментировать с опциями -O2 и -O3, которые еще больше пытаются оптимизировать вашу программу, однако не перестарайтесь. Если ваша программа после такой оптимизации работает еще медленнее, чем до нее, или же некорректно работает, используйте опцию -O0 для отключения оптимизации.
Обо всех остальных опциях вы сможете прочитать в справочной системе.
21.2. Сборочная утилита make.
Если вы уже собирали прикладную программу из исходных кодов, то обратили внимание на стандартную последовательность команд: make; make install.
Без утилиты make не обходится создание ни одного серьезного проекта. Эта утилита управляет сборкой большого проекта, состоящего из десятков и сотен файлов. Программа make может работать не только с компилятором gcc, но и с любым компилятором для любого языка программирования, способным запускаться из командной строки.
Директивы утилиты make служат для определения зависимостей между файлами проекта и находятся в файле по имени Makefile, расположенном в каталоге сборки.
Разберемся, как пишутся make-файлы. Общий формат make-файла выглядит так:
Цель1: список_необходимых_файлов.
Последовательность_команд.
...
ЦельN: список_необходимых_файлов.
Последовательностъ_команд.
Цель — это метка для некоторой последовательности команд (например, install) или результирующий файл, который нужно «построить» — скомпилировать или скомпоновать.
Цели должны отделяться друг от друга хотя бы одной пустой строкой. Список необходимых файлов — это перечень файлов или других целей, которые нужны для достижения данной цели; он может быть и пустым.
Последовательность команд — это команды, которые нужно выполнить для достижения цели. Последовательность команд должна отделяться от начала строки символом табуляции, иначе вы получите ошибку «missing separator» (нет разделителя).
Make-файл может содержать комментарии — они начинаются символом #.
В make-файлах вы можете использовать макроопределения:
CC=gcc.
PATH=/usr/include /usr/src/linux/include.
MODFLAGS:= -O3 -Wall -DLINUX -I$(PATH).
...
$(CC) $(MODFLAGS) -c proga.c.
Чтобы обратиться к макроопределению в команде или в другом макроопределении, нужно использовать конструкцию $(имя). Макроопределение может включать в себя другое, ранее определенное, макроопределение.
Формат запуска утилиты make:
Make [-f файл] [ключи] [цель].
Ключ -f указывает файл инструкций, который нужно использовать вместо Makefile. Если этот ключ не указан, то make ищет в текущем каталоге файл Makefile и начинает собирать указанную цель. Если цель не указана, то выполняется первая встреченная в make-файле. Сборка выполняется рекурсивно: make сначала выполняет все цели, от которых зависит текущая цель. Если зависимость представляет собой файл, то make сравнивает его время последней модификации со временем целевого файла: если целевой файл старше или отсутствует, то будет выполнена указанная последовательность команд. Если целевой файл моложе, то текущая цель считается достигнутой.
Примечание. Если нужно избежать пересборки какого-то из файлов проекта, то можно искусственно «омолодить» его командой touch, которая присвоит ему в качестве времени последней модификации текущее время. Если нужно, наоборот, принудительно пересобрать цель, то следует «омолодить» один из файлов, от которых она зависит.Работа программы make заканчивается, когда достигнута цель, указанная в командной строке. Обычно это цель all, собирающая все результирующие файлы проекта. Другими распространенными целями являются install (установить собранную программу) и clean (удалить ненужные файлы, созданные в процессе сборки).
В листинге 21.2 представлен make-файл, собирающий небольшой проект из двух программ client и server, каждая из которых компилируется из одного файла исходного кода.
Листинг 21.2. Примерный make-файл.
CC=gcc.
CFLAGS=-O.
All: client server.
Client: client.с.
$(CC) client.с -о client.
Server: server.с.
$(CC) server.с -о server.
Обычно при вызове утилиты make не нужно задавать никаких ключей. Но иногда использование ключей бывает очень кстати (таблица 21.1).
Ключи команды make Таблица 21.1.
| Ключ | Назначение |
|---|---|
| -C каталог | Перейти в указанный каталог перед началом работы |
| -d | Вывод отладочной информации |
| -e | Приоритет переменным окружения. Если у нас установлена переменная окружения CC и в Makefile есть переменная с таким же именем, то будет использована переменная окружения |
| -f файл | Использовать указанный файл вместо Makefile |
| -i | Игнорировать ошибки компилятора |
| -I каталог | В указанном каталоге будет производиться поиск файлов, включаемых в Makefile |
| -j n | Запускать не более n команд одновременно |
| -k | Продолжить работу после ошибки, если это возможно |
| -n | Вывести команды, которые должны были выполниться, но не выполнять их |
| -о файл | Пропустить данный файл, даже если в Makefile указано, что он должен быть создан заново |
| -r | Не использовать встроенные правила |
| -s | Не выводить команды перед их выполнением |
| -w | Вывод текущего каталога до и после выполнения команды |
21.3. Пакет binutils и другие полезные программы.
Пакет binutils содержит утилиты для работы с бинарными файлами:
♦ ld — компоновщик: программа, связывающая объектные файлы и библиотеки в исполняемый файл;
♦ ar — работа с архивами (создания, модификация и извлечение);
♦ nm — вывод названий идентификаторов из двоичных файлов;
♦ objcopy — копирование и трансляция двоичных файлов;
♦ objdump — вывод информации из двоичных файлов;
♦ ranlib — генерирование индекса оглавления архива;
♦ size — вывод размеров секций архива или двоичного файла;
♦ strings — вывод строк, которые возможно прочитать, из двоичных файлов;
♦ addr2line — конвертирование адресов в памяти в строку в файле;
♦ nlmconv — конвертирует объектный код в NLM.
А теперь перечислим несколько полезных вспомогательных программ.
21.3.1. ansi2knr.
Утилита ansi2knr предназначена для преобразования текстов программ, написанных в соответствии со стандартом ANSI С, в программы на «классическом» С Кернигана и Ричи. Формат вызова:
Ansi2knr oldfile.c newfile.c.
21.3.2. as.
Программа as — это GNU-версия ассемблера, предназначенная для создания объектных файлов из программ, написанных на языке ассемблера. Формат вызова:
As [ключи] файл1 [файл2 ... файлN].
Ключи программы as Таблица 21.2.
| Ключ | Назначение |
|---|---|
| -а | Вывод листинга |
| -ad | He выводить отладочные сообщения |
| -ad | Включение в листинг текста программы, написанной на языке высокого уровни, если компиляций проводилась с ключом -g |
| -al | Вывод листинга на ассемблере |
| -an | Не обрабатывать форм |
| -as | Вывод списка символов программы |
| -афайл | Вывести листинг в указанный файл |
| -f | Быстрый режим. Директивы препроцессора не обрабатываются |
| -iпуть | Добавить указанный путь к Include-пути |
| -MRI | Обеспечить MRI-совместимость |
| -о файл | Создание объектного файла с указанным именем |
| -R | Поместить сегмент данных в сегмент кода |
| -v | Вывод версии |
| -W | Не выводить предупреждения |
21.3.3. bison.
Программа bison — это грамматический разборщик (парсер): она создает C-программу, предназначенную для разбора определенной грамматики. Данная программа вам не понадобится до тех пор, пока вы не захотите написать собственный компилятор. Ключи программы представлены в таблице 21.3. Формат вызова:
Bison [ключи] файл.
Ключи программы bison Таблица 21.3.
| Ключ | Назначение |
|---|---|
| -b префикс | Использовать указанный префикс для имени входящего файла |
| -d | Создать заголовочный файл, содержащий информацию о типах грамматических образцов (токенов), которые определены в вашей грамматике |
| -I | Не вставлять код в существующие файлы |
| -о файл | Установить файл результата |
| -t | Включить отладочную информацию |
| -v | Записать созданную программу в файл у.out put |
21.3.4. flex.
Flex [параметры] файл.
Это еще одна программа, которая пишет код за нас. Flex может написать программу на языке С, которая будет искать заданные образны текста в текстовых файлах и выполнять определенные действия, заданные программистом. Если вам нужна эта программа, тогда самое время прочитать страницы руководства man flex.
21.3.5. gprof.
Программы вроде gprof называются профайлерами. Они предназначены для определения быстродействия вашей программы. Для каждого вызова функции вашей программы профайлер выводит время ее выполнения. Вы как программист анализируете полученную информацию и, если нужно, оптимизируете исходный код вашей программы.
21.3.6. strip.
Утилита strip удаляет таблицу символов из объектного файла.
21.4. Пример программы на С.
В п. 9.2.3 я сказал о состояниях процесса и перечислил среди них состояние «зомби». Зомби — это процесс, который уже завершился, но его родитель еще не получил сигнала о его завершении и не удалил его структуру из таблицы процессов. Такое может произойти, когда процесс-родитель почему-либо не готов к завершению потомка. Сейчас мы искусственно создадим такого зомби. Процесс-родитель породит потомка и уснет на 10 секунд. Потомок завершится через 2 секунды, а в течение 8 секунд он будет находиться в состоянии зомби. Напоминаю, что состояние процесса можно увидеть по команде top.
Листинг 21.3. Файл zombie.с.
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
Int main() {
int pid;
int status, died;
pid=fork();
switch(pid) {
case -1:
printf("ошибка fork\n");
exit(-1);
case 0:
printf(" Я потомок процесса %d\n", getppid());
printf(" Мой PID %d\n", getpid());
// Ждем 2 секунды и завершаемся.
sleep(2);
exit(0);
default:
printf("Я родитель.\n");
printf("Мой PID %d\n" , getpid());
// Ждем завершения дочернего процесса.
// через 10 секунд, а потом убиваем его.
sleep(10);
if (pid & 1).
kill(pid,SIGKILL);
died = wait(&status);
}
}
Скомпилируйте файл zombie.с и запустите исполняемый файл zombie:
$ gcc -о zombie zombie.с.
$ ./zombie.
Я родитель.
Мой PID 1147.
Я потомок процесса 1147.
Мой PID 1148.
Запомните последний номер и быстро переключитесь на другую консоль, где введите команду top -p 1148:
16:04:22 up 2 min, 3 users, load average: 0,10, 0,10, 0,04.
1 processes: 0 sleeping, 0 running, 1 zombie, 0 stopped.
CPU states: 4,5% user, 7,6% system, 0,0% nice, 0,0% iowait, 87,8% idle.
Mem: 127560k av, 76992k used, 50568k free, 0k shrd, 3872k buff.
24280k active, 19328k inactive.
Swap: 152576k av, 0k used, 152576k free 39704k cached.
PID USER PRI NI SIZE RSS SHARE STAT %CPU %MEM TIME COMMAND.
1148 den 17 0 0 0 0 Z 0,0 0,0 0:00 zombie <defunct>
Мы видим, что в списке процессов появился один зомби (STAT = Z), который «проживет» в таком состоянии целых 8 секунд.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
- 385.
- 386.
- 387.
- 388.
- 389.
- 390.
- 391.
- 392.
- 393.
- 394.
- 395.
- 396.
- 397.
- 398.
- 399.
- 400.
- 401.
- 402.
- 403.
- 404.
- 405.
- 406.
- 407.
- 408.
- 409.
- 410.
- 411.
- 412.
- 413.
- 414.
- 415.
- 416.
- 417.
- 418.
- 419.
- 420.
- 421.
- 422.
- 423.
- 424.
- 425.
- 426.
- 427.
- 428.
- 429.
- 430.
- 431.
- 432.
- 433.
- 434.
- 435.
- 436.
- 437.
- 438.
- 439.
- 440.
- 441.
- 442.
- 443.
- 444.
- 445.
- 446.
- 447.
- 448.
- 449.
- 450.
- 451.
- 452.
- 453.
- 454.
- 455.
- 456.
- 457.
- 458.
- 459.
- 460.
- 461.
- 462.
- 463.
- 464.
- 465.
- 466.
- 467.
- 468.
- 469.
- 470.
- 471.