Марк Митчелл - Программирование для Linux. Профессиональный подход
В нашем тестовом проекте reciprocal три очевидных целевых модуля: reciprocal.o, main.o и сама программа reciprocal. Правила нам уже известны: это рассмотренные выше командные строки. А вот над зависимостями нужно немного подумать. Ясно, что файл reciprocal зависит от файлов reciprocal.o и main.o, поскольку нельзя скомпоновать программу, не создав оба объектных файла. Последние должны перестраиваться при изменении соответствующих исходных файлов. Нельзя также забывать о файле reciprocal.hpp: он включается в оба исходных файла, поэтому его изменение тоже затрагивает объектные файлы.
Помимо очевидных целевых модулей должен также существовать модуль clean. Он предназначен для удаления всех сгенерированных объектных файлов и программ, чтобы можно было начать все сначала. Правило для данного модуля включает команду rm, удаляющую перечисленные файлы.
Чтобы передать всю эту информацию утилите make, необходимо создать файл Makefile. Его содержимое будет таким:
reciprocal: main.o reciprocal.o
g++ $(CFLAGS) -о reciprocal main.o reciprocal.o
main.o: main.c reciprocal.hpp
gcc $(CFLAGS) -c main.c
reciprocal.o: reciprocal.cpp reciprocal.hpp
g++ $(CFLAGS) -c reciprocal.cpp
clean:
rm -f *.o reciprocal
Целевые модули перечислены слева. За именем модуля следует двоеточие и существующие зависимости. В следующей строке указано правило, по которому создается модуль (назначение записи $(CFLAGS) мы пока проигнорируем). Строка правила должна начинаться с символа табуляции, иначе утилита make проинтерпретирует ее неправильно.
Если удалить созданные нами выше объектные файлы и ввести
% make
будет получен следующий результат:
% make
gcc -c main.c
g++ -c reciprocal.cpp
g++ -o reciprocal main.o reciprocal.o
Утилита make автоматически создала объектные файлы и скомпоновала их. Попробуйте теперь внести какое-нибудь простейшее изменение в файл main.c и снова запустить утилиту. Вот что произойдет:
% make
gcc -с main.c
g++ -о reciprocal main.o reciprocal.o
Как видите, утилита make повторно создала файл main.o и перекомпоновала программу, но не стала перекомпилировать файл reciprocal.cpp, так как в этом не было необходимости.
Запись $(CFLAGS) обозначает переменную утилиты make. Ее можно определить либо в файле Makefile, либо в командной строке. Утилита подставит на место переменной реальное значение во время выполнения правила. Вот как, например, можно осуществить перекомпиляцию с включённой оптимизацией:
% make clean
rm -f *.o reciprocal
% make CFLAGS=-O2
gcc -O2 -c main.c
g++ -O2 -c reciprocal.cpp
g++ -O2 -o reciprocal main.o reciprocal.o
Обратите внимание на то, что вместо записи $(CFLAGS) в правилах появился флаг -O2.
В этом разделе мы рассмотрели лишь самые основные возможности утилиты make. Чтобы получить о ней более подробную информацию, обратитесь к интерактивной документации, введя такую команду:
% info make
В документации можно найти полезные сведения о том, как упростить управление файлом Makefile, уменьшить число необходимых правил и автоматически вычислять зависимости.
1.4. GNU-отладчик gdb
Отладчик — это программа, с помощью которой можно узнать, почему написанная вами программа ведет себя не так, как было задумано. Работать с отладчиком приходится очень часто. Большинство Linux-программистов имеет дело с GNU-отладчиком (GNU Debugger, GDB), который позволяет пошагово выполнять программу, создавать точки останова и проверять значения локальных переменных.
1.4.1. Компиляция с включением отладочной информации
Чтобы можно было воспользоваться GNU-отладчиком, необходимо скомпилировать программу с включением в нее отладочной информации. Этой цели служит опция -g компилятора. Если имеется описанный выше файл Makefile, достаточно задать переменную CFLAGS равной -g при запуске утилиты make:
% make CFLAGS=-g
gcc -g -с main.c
g++ -g -c reciprocal.cpp
g++ -g -о reciprocal main.o reciprocal.o
Встречая в командной строке флаг -g, компилятор включает дополнительную информацию в объектные и исполняемые файлы. Благодаря этой информации отладчик узнает, какие адреса соответствуют тем или иным строкам в том или ином исходном файле, как отобразить значение локальной переменной, и т.д.
1.4.2. Запуск отладчика
Отладчик gdb запускается следующим образом:
% gdb reciprocal
После запуска появится строка приглашения такого вида:
(gdb)
В первую очередь необходимо запустить программу под отладчиком. Для этого введите команду run и требуемые аргументы. Попробуем вызвать программу без аргументов:
(gdb) run
Starting program: reciprocal
Program received signal SIGSEGV, Segmentation fault.
__strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0)
at strtol.c:287
287 strtol.c: No such file or directory.
(gdb)
Проблема заключается в том, что в функции main() не предусмотрены средства контроля ошибок. Программа ожидает наличия аргумента, а в данном случае его нет. Получение сигнала SIGSEGV означает крах программы. Отладчик определяет, что причина краха находится в функции __strtol_internal(). Эта функция является частью стандартной библиотеки, но ее исходный файл отсутствует. Вот почему появляется сообщение "No such file or directory". С помощью команды where можно просмотреть содержимое стека:
(gdb) where
#0 __strtol_internal (nptr=0x0, endptr=0x0, base=10, group=0)
at strtol.c:287
#1 0x40096fb6 in atoi (nptr=0x0) at ../stdlib/stdlib.h:251
#2 0x804863e in main (argc=1, argv=0xbffff5e4) at main.c:8
Как нетрудно заметить, функция main() вызвала функцию atoi(), передав ей нулевой указатель, что и стало причиной ошибки.
С помощью команды up можно подняться но стеку на два уровня, дойдя до функции main():
(gdb) up 2
#2 0x804863е in main (argc=1, argv=0xbffff5e4) at main.c:8
8 i = atoi(argv[1]);
Заметьте, что отладчик нашел исходный файл main.c и отобразил строку, где располагается ошибочный вызов функции. Узнать значение нужной локальной переменной позволяет команда print:
(gdb) print argv[1]
$2 = 0x0
Это подтверждает нашу догадку о том, что причина ошибки — передача функции atoi() указателя NULL.
Установка контрольной точки осуществляется посредством команды break:
(gdb) break main
Breakpoint 1 at 0x804862e: file main.c, line 8.
В данном случае контрольная точка размещена в первой строке функции main(). Давайте теперь заново запустим программу, передав ей один аргумент:
(gdb) run 7
Starting program: reciprocal 7
Breakpoint 1, main (argc=2, argv=0xbffff5e4) at main.c:8
8 i = atoi(argv[1]);
Как видите, отладчик остановился на контрольной точке- Перейти на следующую строку можно с помощью команды next:
(gdb) next
9 printf("The reciprocal of %d is %gn", i,
reciprocal(i));
Если требуется узнать, что происходит внутри функции reciprocal(), воспользуйтесь командой step:
(gdb) step
reciprocal (i=7) at reciprocal.cpp:6
6 assert(i != 0);
Иногда удобнее запускать отладчик gdb непосредственно из редактора Emacs, а не из командной строки. Для этого следует ввести в редакторе команду M-x gdb. Когда отладчик останавливается в контрольной точке, редактор Emacs автоматически открывает соответствующий исходный файл. Не правда ли. проще разобраться в происходящем, глядя на весь файл, а не на одну его строку?
1.5. Поиск дополнительной информации
В каждый дистрибутив Linux входит масса полезной документации. В ней можно прочесть почти все из того, о чем говорится в этой книге (хотя это, очевидно, займет больше времени). Документация не всегда хорошо организована, поэтому поиск нужной информации требует определенной изобретательности. Иногда представленные факты оказываются устаревшими, так что не стоит всему слепо верить.
Ниже описаны наиболее полезные источники информации о программировании в Linux.
1.5.1. Интерактивная документация
В дистрибутивы Linux входят man-страницы с описанием большинства стандартных команд, системных вызовов и стандартных библиотечных функций. Интерактивная документация разбита на разделы, которым присвоены номера. Для программистов наиболее важными являются следующие разделы:
(1) пользовательские команды;