
LINUX / KOZLOV2_1
.pdfНеобходимо отметить, что цель по умолчанию поддерживается не всеми версиями gnu make, поэтому если на экране будет выведен лишь один вызов компилятора, попробуйте добиться полной сборки проекта, введя одну из перечисленных ниже команд (вообще говоря, эквивалентных):
$ make all
$ make -f Makefile
$ make -f Makefile all
Проверим работоспособность проекта: $ ../build/proj
I'm library number 1
Library number 1, MOM-macro was defined I'm library number 2
Library number 2, MTM-macro was defined
Исполняемый файл работает верно, значит, задача сохранения CXXFLAGS, поставленная при обсуждении файла $(Root)/build-system/compilation.mk, решена успешно.
Изучим содержимое вновь созданной папки build: $ cd ../build; ls -l
-rwxr-xr-x 1 diss diss 9930 2009-11-22 14:36 proj drwxr-xr-x 4 diss diss 4096 2009-11-22 14:34 src
В папке находятся исполняемый файл проекта и директория src: $ cd src; ls -l
drwxr-xr-x 2 diss diss 4096 2009-11-22 14:36 module1 drwxr-xr-x 2 diss diss 4096 2009-11-22 14:36 module2 $ cd module1; ls -l
-rw-r--r-- 1 diss diss 299 2009-11-22 14:36 lib1.d -rw-r--r-- 1 diss diss 1836 2009-11-22 14:36 lib1.o -rw-r--r-- 1 diss diss 417 2009-11-22 14:36 main.d -rw-r--r-- 1 diss diss 1068 2009-11-22 14:36 main.o
Мы видим объектные файлы, входящие в первый модуль, и для каждого из них файлы с описанием зависимостей:
$ more main.d
/home/diss/Book/Build_system/example/src/module1/main.cpp: /home/diss/Book/Build_system/example/src/module1/lib1.h: /home/diss/Book/Build_system/example/src/module2/lib2.h: /home/diss/Book/Build_system/example/build/src/module1/main.o:
191
PDF created with pdfFactory Pro trial version www.pdffactory.com
/home/diss/Book/Build_system/example/src/module1/main.cpp \ /home/diss/Book/Build_system/example/src/module1/lib1.h \ /home/diss/Book/Build_system/example/src/module2/lib2.h
Теперь проверим, дает ли ожидаемые преимущества использование утилиты make. $ cd ../../../build-system
$ make
make: Nothing to be done for `all'.
Make сообщает нам о том, что никакие из файлов, входящих в проект, не обновлялись с последней сборки и участие компилятора не требуется. Обновим файл lib2.cpp:
$ touch ../src/module2/lib2.cpp
Теперь согласно выдвинутым принципам вызов make должен инициировать компиляцию lib2.cpp и компоновку проекта:
$ make
g++ -c -DMTM -I/home/diss/Book/Build_system/example /home/diss/Book/Build_system/example/src/module2/lib2.cpp -o /home/diss/Book/Build_system/example/build/src/module2/lib2.o g++ /home/diss/Book/Build_system/example/build/src/module1/lib1.o /home/diss/Book/Build_system/example/build/src/module1/main.o /home/diss/Book/Build_system/example/build/src/module2/lib2.o -o /home/diss/Book/Build_system/example/build/proj
Теперь обновим интерфейсный файл lib2.h и вызовем make. При этом должно произойти следующее: компилирование lib2.cpp и main.cpp, а также компоновка проекта:
$ touch ../src/module2/lib2.h $ make
g++ -c -DMOM -I/home/diss/Book/Build_system/example /home/diss/Book/Build_system/example/src/module1/main.cpp -o /home/diss/Book/Build_system/example/build/src/module1/main.o g++ -c -DMTM -I/home/diss/Book/Build_system/example /home/diss/Book/Build_system/example/src/module2/lib2.cpp -o /home/diss/Book/Build_system/example/build/src/module2/lib2.o g++ /home/diss/Book/Build_system/example/build/src/module1/lib1.o /home/diss/Book/Build_system/example/build/src/module1/main.o /home/diss/Book/Build_system/example/build/src/module2/lib2.o -o /home/diss/Book/Build_system/example/build/proj
Таким образом, make выполняет возложенные на нее обязанности.
192
PDF created with pdfFactory Pro trial version www.pdffactory.com
Удалим все созданные файлы: $ make clean
rm -rf /home/diss/Book/Build_system/example/build/src/module1/lib1.d /home/diss/Book/Build_system/example/build/src/module1/main.d /home/diss/Book/Build_system/example/build/src/module1/lib1.o /home/diss/Book/Build_system/example/build/src/module1/main.o /home/diss/Book/Build_system/example/build/src/module2/lib2.d /home/diss/Book/Build_system/example/build/src/module2/lib2.o /home/diss/Book/Build_system/example/build/proj
12.3. Библиотеки
Библиотека - это набор скомпонованных особым образом объектных файлов. Библиотеки создаются для того, чтобы иметь возможность использовать однажды реализованный функционал во многих программах.
Различают следующие типы библиотек:
∙статические – это архив объектных файлов. При компоновке программы использующей статическую библиотеку, объектные файлы извлекаются из архива и компонуются вместе с обычными объектными файлами;
∙динамические – отличаются от статических тем, что при компоновке, код таких
библиотек не включается в выходной файл. В выходной файл включаются ссылки на динамическую библиотеку.
У каждого типа библиотек имеются свои преимущества и недостатки. Статические библиотеки делают программу автономной, так как весь исполняемый код, необходимый для работы программы, уже в наличии. Программа, скомпонованная с динамическими библиотеками, наоборот, требует для запуска наличия в окружении нужных библиотек, но при этом использование динамических библиотек дает преимущество при модернизации: достаточно собрать новую версию библиотеки и все программы, использующие ее, получат обновления.
В операционной системе Linux имеется соглашение, согласно которому имя библиотеки должно начинаться с приставки lib. Статическая библиотека должна иметь окончание .a, динамическая - .so.
Рассмотрим процесс создания библиотек на примере небольшого проекта, состоящего из нижеперечисленных файлов.
193
PDF created with pdfFactory Pro trial version www.pdffactory.com
Первая пара файлов есть интерфейс и реализация метода, выводящего в консоль некоторое сообщение.
1 // file: lib1.h
2
3#ifndef LIB1_H
4#define LIB1_H
6void present_lib_1();
8#endif // LIB1_H
1:комментарий, сообщающий имя файла;
3, 8: так как в языке С++ существует правило одного определения, необходимо защититься от множественного включения данного файла. Множественное включение может произойти, если с помощью директивы #include данный файл будет подключен более одного раза в каком-то другом файле. Встретив директиву #ifndef, препроцессор включит исходный код, заключенный между строками #ifndef LIB1_H/#endif, только в том случае, если символьная метка LIB1_H не была определена;
4: если препроцессор дошел до этой строки, то файл включается первый раз и это необходимо отметить во избежание повторного включения. Директива #define определяет символьную метку LIB1_H;
6: объявление функции present_lib_1. Функция принимает нуль аргументов и ничего не возвращает.
1 // file: lib1.cpp
2
3#include «lib1.h»
4#include <iostream>
6void present_lib_1()
7{
8std::cout << «I'm library number 1\n»;
9}
1:комментарий сообщающий имя файла. Данный файл содержит реализацию описанной в файле lib1.h функции present_lib_1;
3:подключаем объявление реализуемой функции;
194
PDF created with pdfFactory Pro trial version www.pdffactory.com
4: так как реализация функции present_lib_1 использует вывод в стандартный поток cout (консоль), то по стандарту необходимо подключить соответствующий файл стандартной библиотеки;
8: вывод (<<) в консоль (std::cout) строки «I'm library number 1»;
Пара файлов lib2.h/lib2.cpp имеет аналогичное паре lib1.h/lib1.cpp содержание с точностью до переименования 1 в 2:
1 // file: lib2.h
2
3#ifndef LIB2_H
4#define LIB2_H
6void present_lib_2();
8#endif // LIB2_H
1// file: lib2.cpp
3#include «lib2.h»
4#include <iostream>
6void present_lib_2()
7{
8std::cout << «I'm library number 2\n»;
9}
Вфункции main производятся вызовы функций present_lib_1 и present_lib_2:
1// file: main.cpp
2
3#include «lib1.h»
4#include «lib2.h»
6int main()
7{
8present_lib_1();
9present_lib_2();
10
195
PDF created with pdfFactory Pro trial version www.pdffactory.com
11return 0;
12}
1:комментарий, сообщающий имя файла;
3:так как в теле main производится вызов функции present_lib_1, то по стандарту нужно подключить заголовочный файл, в котором она объявлена.
4:аналогично строке 3, но для функции present_lib_2.
6: одна из стандартных сигнатур функции main (принимает нуль аргументов, возвращает целое число);
8:вызов present_lib_1;
9:вызов present_lib_2;
11: возврат нуля (успешное завершение).
Таким образом, программа выводит на экран пару строк: I'm library number 1
I'm library number 2
Допустим, что lib1 используется как статическая библиотека. Создадим ее. Скомпилируем объектный файл:
$ g++ -c lib1.cpp -o lib1.o
-c - указание выполнить компиляцию; lib1.cpp - имя файла с исходным кодом; -o lib1.o - имя выходного файла.
Наконец, создадим статическую библиотеку: $ ar rcs liblib1.a lib1.o
r - заменять файлы в архиве в случае присутствия;
c - не выводить предупреждение в случае создания архива;
s - создать индексную таблицу, в которой будут перечислены файлы, входящие в архив. Этой информацией воспользуется компоновщик, когда будет использовать библиотеку в своей работе;
liblib1.a - имя библиотеки;
lib1.o - имя объектного файла, входящего в библиотеку.
Допустим, что lib2 используется как динамическая библиотека. Прежде всего, скомпилируем объектный файл:
$ g++ -fpic -c lib2.cpp -o lib2.o
Смысл параметров передаваемых компилятору тот же, что и в случае со статической библиотекой, появилась лишь одна новая опция: -fpic. Эта опция указывает компилятору генерировать position independent code (PIC). Это особый вид объектного
196
PDF created with pdfFactory Pro trial version www.pdffactory.com
кода, который необходим для того чтобы данные из динамической библиотеки можно было подключать в момент запуска программы.
Далее создадим динамическую библиотеку: $ g++ -shared lib2.o -o liblib2.so
-shared - указание создавать динамическую библиотеку; lib2.o - имя объектного файла, входящего в библиотеку; -o liblib2.so - имя выходного файла.
Теперь скомпилируем основной модуль: $ g++ -c main.cpp -o main.o
-с - выполнить компиляцию;
main.cpp - имя файла с исходным кодом; -o main - имя выходного файла.
Осталось лишь скомпоновать основной модуль с библиотеками: $ g++ -L. main.o -llib1 -llib2 -o prog
-L. - указание компоновщику на то, что библиотеки надо искать в текущей директории; -llib1 -llib2 - указание использовать библиотеки с данными именами;
-o prog - имя выходного файла.
Необходимо отметить, что имеются свои тонкости в том, в каком порядке перечислять объектные файлы и библиотеки.
Полезной при работе с динамическими библиотеками является утилита ldd. Она позволяет посмотреть зависимость файла от динамических библиотек. Например, в нашем
случае
$ ldd prog
linux-gate.so.1 => (0xb7f1f000) liblib2.so => not found
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e1d000) libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7df6000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7de7000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7c84000) /lib/ld-linux.so.2 (0xb7f20000)
ldd сообщает о том, что наша программа использует в своей работе семь динамических библиотек. При этом библиотека по имени lib2 не была найдена.
При запуске любого приложения, использующего динамические библиотеки, поиск библиотек осуществляется в каталогах /lib и /usr/lib. Чтобы обойти это ограничение,
197
PDF created with pdfFactory Pro trial version www.pdffactory.com
имеется переменная LD_LIBRARY_PATH, в которую нужно добавить путь до папок с дополнительными библиотеками.
Согласно ответу ldd наша динамическая библиотека lib2 не была найдена, добавим путь до нее к LD_LIBRARY_PATH:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
Проверим: $ ldd prog
linux-gate.so.1 => (0xb7f67000) liblib2.so (0xb7f62000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e62000) libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7e3b000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7e2c000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7cc9000) /lib/ld-linux.so.2 (0xb7f68000)
Запустим программу: $ ./prog
I'm library number 1 I'm library number 2
Видно, что программа работает, как ожидалось.
Контрольные вопросы
1.Как организовать хранение программного кода?
2.Какие задачи позволяет решать система сборки?
3.Зачем нужны библиотеки?
198
PDF created with pdfFactory Pro trial version www.pdffactory.com
13.Отладка и оптимизация приложений
13.1.Отладка приложений
Впроцессе написания программы в нее вносятся ошибки. Поэтому необходимо некоторое средство, предоставляющее инструменты для поиска ошибок. Таким инструментом является отладчик. Отладчик позволяет выполнять программу по шагам и отслеживать внутреннее состояние программы (значение переменных и т. д.) в динамике. Популярной утилитой для отладки в среде Linux является gdb.
Обобщенная схема вызова утилиты выглядит следующим образом: gdb [options] --args prog [arguments]
[options] - опции (см. man gdb);
--args - указание на то, что в функцию main, отлаживаемой программы prog, передаются следующие аргументы [arguments].
Для начала, перечислим команды gdb, выполняющие основные действия:
∙справка - help;
∙запуск отладки - run;
∙вывод исходного кода - list;
∙установка точки останова - break;
∙выполнение некоторого количество строк исходного кода - step;
∙продолжение выполнения программы после остановки - continue;
∙вывод значения выражения (переменной) - print;
∙Отмена точек останова - disable.
Для того чтобы получить подробное описание команды, выполните следующую команду: help [command name] (например, help run).
Рассмотрим возможности утилиты на примере программы вычисления факториала: 1 // file: main.cpp
2
3 #include <iostream>
4
5long factorial(long n)
6{
7return n == 0 ? 1 : n * factorial(n-1);
8}
199
PDF created with pdfFactory Pro trial version www.pdffactory.com
9
10int main()
11{
12const int LIM = 10;
14for(int i = -1; i < LIM; ++i)
15{
16std::cout << i << «!:\t»
17 |
<< factorial(i) << «\n»; |
18 |
} |
19 |
|
20return 0;
21}
1:комментарий с именем файла;
3: так как программа использует вывод в консоль, то необходимо подключить стандартный заголовок для работы с ней (iostream);
5: функция подсчета факториала, принимает целое (long n), возвращает его факториал
(long);
7: возвратить (return), если результат проверки (n == 0) положительный - 1, иначе умножить n на число, возвращенное после вызова этой же функции, но с аргументом,
уменьшенным на 1 (factorial(n-1));
10: функция main принимает нуль аргументов, возвращает целое;
12: целочисленная (int) константа (const), определяющая максимальное число, для которого будет вычисляться факториал;
14: выполнять цикл с начальным условием (int i = -1), пока выполняется ограничение (i < LM), при этом после каждой итерации увеличивать i на единицу (++i);
16:вывод (<<) сообщения «i!:» в консоль (std::cout);
17:вывод значения факториала, которое возвращает вызов функции (factorial(i)).
20:возврат из main с успешным кодом (0).
Для того чтобы отладчик мог выполнять свою работу, компилятор должен создать особый вид исполняемого кода. В случае использования gcc для создания отладочного исполняемого кода необходимо выполнить следующую команду:
g++ -g main.cpp -o proj
-g - включить отладочную информацию; main.cpp - имя файла с исходным кодом;
200
PDF created with pdfFactory Pro trial version www.pdffactory.com