Внутреннее устройство Linux
Шрифт:
Объектный файл является двоичным файлом, который процессор уже почти готов понять, если учесть еще несколько моментов. Во-первых, операционная система не знает, как запускать объектные файлы, а во-вторых, вам, вероятно, потребуется скомбинировать несколько объектных файлов и системных библиотек, чтобы создать завершенную программу.
Чтобы создать полностью функционирующий исполняемый файл из одного или нескольких объектных файлов, следует запустить компоновщик, команду ld в Unix. Программисты редко используют эту команду в командной строке, поскольку компилятор C знает, как запускать компоновщик. Для создания исполняемого файла с названием myprog из двух приведенных выше объектных файлов запустите такую команду:
$ cc -o myprog main.o aux.o
Хотя
15.1.2. Заголовочные файлы (Include) и каталоги
Заголовочные файлы C являются дополнительными файлами с исходным кодом, который обычно содержит объявления типов и библиотечных функций. Например, файл stdio.h является заголовочным (см. простую программу в разделе 15.1).
К сожалению, с заголовочными файлами связано большое число проблем при компиляции. Большинство глюков возникает, когда компилятор не может отыскать заголовочные файлы и библиотеки. Бывают даже случаи, когда программист забывает подключить необходимый заголовочный файл, это приводит к тому, что исходный код не компилируется.
Исправление проблем, вызванных включаемыми файлами
Отследить правильный включаемый файл не всегда легко. Иногда несколько включаемых файлов с одинаковыми именами расположены в разных каталогах и неясно, какой из них правильный. Когда компилятор не может обнаружить включаемый файл, сообщение об ошибке выглядит так:
badinclude.c:1:22: fatal error: notfound.h: No such file or directory
Это сообщение говорит о том, что компилятор не может найти заголовочный файл notfound.h, на который ссылается файл badinclude.c. Эта ошибка является прямым следствием такой директивы в первой строке файла badinclude.c:
#include <notfound.h>
По умолчанию в Unix каталогом для включаемых файлов является /usr/include; компилятор всегда просматривает его, если вы явно не укажете ему не выполнять этого. Тем не менее можно настроить компилятор так, чтобы он просматривал другие каталоги (большинство каталогов с заголовочными файлами содержит слово include где-либо в своем имени).
примечание
Из главы 16 вы узнаете о том, как отыскать отсутствующие включаемые файлы.
Предположим, вы обнаружили файл notfound.h в каталоге /usr/junk/include. Можно сделать так, чтобы компилятор видел этот каталог с помощью параметра -I:
$ cc -c -I/usr/junk/include badinclude.c
Теперь компилятор не должен спотыкаться на строке кода в файле badinclude.c, которая ссылается на заголовочный файл.
Следует также опасаться включаемых файлов, использующих двойные кавычки (" ") вместо угловых скобок (< >), например так:
#include "myheader.h"
Двойные кавычки означают, что заголовочный файл не располагается в системном каталоге для включаемых файлов и компилятору следует поискать его путь. Часто это говорит о том, что включаемый файл находится в том же каталоге, что и файл с исходным кодом. Если вам встретится проблема с двойными кавычками, то, вероятно, вы пытаетесь скомпилировать неполный исходный код.
Что такое препроцессор C (cpp)?
Оказывается, компилятор C не выполняет работу по отыскиванию всех этих включаемых файлов. Эта задача приходится на долю препроцессора C — команды, которую компилятор применяет к исходному коду, прежде чем выполнить синтаксический анализ реальной программы. Препроцессор перезаписывает исходный код в такой форме, которую способен понять компилятор; это инструмент, делающий исходный код более легким для чтения (и снабжающий его обходными маневрами).
Команды препроцессора в исходном коде называются директивами, они начинаются с символа #. Существуют три основных типа директив.
• Включаемые файлы. Директива #include дает препроцессору указание о том, чтобы он включил весь файл. Обратите внимание на то, что флаг компилятора -I является в действительности параметром, который вынуждает препроцессор искать включаемые файлы в указанном каталоге, как вы видели в предыдущем разделе.
• Макроопределения. Строка, подобная #define BLAH something, говорит препроцессору о том, чтобы он выполнил замену всех вхождений элемента BLAH на элемент something в исходном коде. По соглашению названия макроопределений даются прописными буквами, но не следует удивляться тому, что программисты иногда используют макроопределения, имена которых похожи на функции и переменные. Сплошь и рядом это причиняет массу неприятностей. Многие программисты превращают в спорт неправильное использование препроцессора.
примечание
Вместо того чтобы приводить макроопределения в исходном коде, можно также передавать параметры в компилятор: команда -DBLAH=something будет работать так же, как приведенная выше директива.
• Условные операторы. Можно пометить отдельные фрагменты кода с помощью слов #ifdef, #if и #endif. Директива #ifdef MACRO проверяет, определено ли макроопределение MACRO для препроцессора, а директива #if condition проверяет, является ли результат условия condition ненулевым. Для обеих директив в том случае, когда условие, следующее за директивой if, является ложным, препроцессор не передает компилятору текст программы, который расположен между директивами #if и #endif. Следует привыкнуть к этому, если вы собираетесь исследовать какой-либо код на языке C.
Приведу далее пример условной директивы. Когда препроцессор встречает такой код, он проверяет, есть ли макроопределение DEBUG, и если оно определено, передает компилятору строку, содержащую команду fprintf. В противном случае препроцессор пропускает эту строку и продолжает обработку файла после директивы #endif:
#ifdef DEBUG
fprintf(stderr, "This is a debugging message.\n");
#endif
примечание
Препроцессор C ничего не знает о синтаксисе языка C, переменных, функциях и других элементах. Он понимает только свои собственные макроопределения и директивы.
В Unix препроцессор C называется cpp, но можно также запускать его с помощью команды gcc -E. Однако вам нечасто понадобится запускать препроцессор как таковой.
15.1.3. Связывание с библиотеками
Компилятор C знает о вашей системе недостаточно для того, чтобы самостоятельно создать пригодную программу. Для построения завершенных программ вам необходимы библиотеки. Библиотека C является набором распространенных, заранее скомпилированных функций, которые можно встраивать в программу. Например, многие исполняемые файлы используют библиотеку math, поскольку она обеспечивает работу с тригонометрическими и другими функциями.