Если функция ссылается на идентификатор, описанный как
extern, то где-то среди файлов или библиотек, образующихполную программу, должно содержаться внешнее определениеэтого идентификатора. Все функции данной программы, которыессылаются на один и тот же внешний идентификатор, ссылаютсяна один и тот же объект, так что следует позаботиться, чтобыспецифицированные в этом определении тип и размер были сов-местимы с типом и размером, указываемыми в каждой функции,которая ссылается на эти данные. Появление ключевого слова
extern во внешнем определенииуказывает на то, что память для описанных в нем идентифика-торов будет выделена в другом файле. Следовательно, в состо-ящей из многих файлов программе внешнее определение иденти-фикатора, не содержащее спецификатора
extern, должно появ-ляться только в одном из этих файлов. Любые другие файлы,которые желают дать внешнее определение этого идентифика-тора, должны включать в это определение слово
extern. Иден-тификатор может быть инициализирован только в том описании,которое приводит к выделению памяти. Из этого правила в
ОС ДЕМОС имеется исключение. Внешнийобъект может присутствовать в нескольких описаниях без
extern. При этом длина объекта в разных описаниях должнасовпадать, а инициализация, если она есть, должна прово-диться ровно в одном из описаний. При нарушении этих правилбудет выдана ошибка на этапе редактировании связей прог-раммы. Идентификаторы, внешнее определение которых начинаетсясо слова
static, недоступны из других файлов. Функции могутбыть описаны как
static.
Неявные описания
Не всегда необходимо специфицировать и класс памяти итип идентификатора в описании. Во внешних определениях иописаниях формальных параметров и членов структур класспамяти определяется по контексту. Если в находящемся внутрифункции описании не указан тип, а только класс памяти, топредполагается, что идентификатор имеет тип
int; если неуказан класс памяти, а только тип, то идентификатор предпо-лагается описанным как
auto. Исключение из последнего пра-вила дается для функций, потому что спецификатор
auto дляфункций является бессмысленным (язык Си не в состоянии ком-пилировать программу в стек); если идентификатор имеет тип
функция,
возвращающая..., то он предполагается неявно опи-санным как
extern. Входящий в выражение и неописанный ранее идентификатор,за которым следует скобка
(, считается описанным по
-42- контексту как
функция,
возвращающая int. /*
extern */
int tab [100];
static /*
int */
t1; /*
int */
func (
i) /*
int i; */
{ register /*
int */
k; /*
auto */
char buf [512]; /*
extern int f1 (); */...
f1 (
a,
b)...
* 9. ПРЕПРОЦЕССОР ЯЗЫКА 'СИ'
Компилятор языка Си содержит препроцессор, который поз-воляет осуществлять макроподстановки, условную компиляцию ивключение именованных файлов. Строки, начинающиеся с
#,являются командами этого препроцессорa. Синтаксис этих строкне связан с остальным языком; они могут появляться в любомместе и их влияние распространяется (независимо от областидействия) до конца исходного программного файла. Фактическипрепроцессор расширяет возможности языка Си, реализуя такиефункции, которые в других языках входят в состав самогоязыка (например, параметрические константы в Фортране-77).
Замена лексем
Команда
#define идентификатор строка _
лексем (обратите внимание на отсутствие в конце точки с запятой)приводит к тому, что препроцессор заменяет последующие вхож-дения этого идентификатора на указанную строку лексем.Строка вида
#define идентификатор (
идентифика -
тор,...,
идентификатор)
строка _
лексем где между первым идентификатором и открывающейся скобкой "("нет пробела, представляет собой макроопределение с аргумен-тами. В дальнейшем первый идентификатор, за которым следуетоткрывающая скобка "(", последовательность разделенных запя-тыми лексем и закрывающая скобка ")", заменяются строкойлексем из определения. Каждое вхождение идентификатора, упо-мянутого в списке формальных параметров в определении, заме-няется соответствующей строкой лексем из обращения. Факти-ческими аргументами в обращении являются строки лексем, раз-деленные запятыми; однако запятые, входящие в закавыченныестроки или заключенные в круглые скобки, не разделяют аргу-ментов. Количество формальных и фактических параметровдолжно совпадать. Текст внутри строки или символьной конс-танты не подлежит замене.
-43- В обоих случаях замененная строка просматривается сновас целью обнаружения других идентификаторов, известных преп-роцессору. В обоих случаях слишком длинная строка определе-ния может быть продолжена на другой строке, если поместить вконце продолжаемой строки обратную косую черту "
\ ". Описываемая возможность особенно полезна для определе-ния "объявляемых констант", как, например,
#define TABSIZE 100
int table [TABSIZE]; или для замены некоторых функций с помощью макроподстановки:
#define max (
a,
b) ((
a)>(
b)?(
a):(
b))
x =
max (
y,20) (в последнем определении
a и
b взяты в скобки, для того,чтобы фактическими параметрами макро могли бы быть произ-вольные выражения. Команда
#undef идентификатор приводит к отмене препроцессорного определения данного иден-тификатора. Определить идентификатор можно не только с помощьюкоманды
#define, но также и при вызове компилятора, спомощью параметров команды
cc.
Включение файлов
Команда
#include "
filename " приводит к замене этой строки на все содержимое файла с име-нем
filename. Файл с этим именем сначала ищется в текущемсправочнике, а затем в других "стандартных" местах, опреде-ляемых пользователем при вызове компилятора. В отличие отэтого команда
#include <
filename > ищет файл только в стандартном справочнике системы. В
ОС ДЕМОС файл ищется в справочнике /
usr /
include. Команды
#include могут быть вложенными.
-44-
Условная компиляция
Команда препроцессора
#if константное выражение проверяет, отлично ли от нуля значение константного выраже-ния. Команда:
#ifdef идентификатор проверяет, определен ли этот идентификатор в настоящиймомент в препроцессоре, т.е. определен ли этот идентификаторс помощью команды
#define. Команда:
#ifndef идентификатор проверяет, является ли этот идентификатор в данный момент неопределенным для препроцессора. За каждым из трех перечисленных видов строк может сле-довать произвольное число строк, возможно содержащих командупрепроцессора
#else а затем должна следовать команда:
#endif Если проверяемое условие истинно, то любые строки между
#else и
#endif игнорируются. Если проверяемое условие ложно,то любые строки между проверяемой строкой и
#else или, приотсутствии
#else,
#endif игнорируются. Эти конструкции могут быть вложенными. Например:
#ifdef DEBUG fprintf (
stderr,"
i =%
o j =%
d\n ",
i,
j);
#endif Переменная препроцессора может быть определена не только всамой программе, но и при вызове транслятора.
Команда #line
Для других препроцессоров, генерирующих Си-программы,полезна следующая команда:
#line константа "имя _
файла "
-45- которая сообщает компилятору (для диагностических сообще-ний), что следующая строка исходного файла имеет номер,задаваемый константой, и что текущий входной файл именуется
именем _
файла. Если
имя _
файла отсутствует, то запоминаемоеимя файла не изменяется. Пример:
#line 250 "
gram.
y "
* 10. ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ О ТИПАХ
В этом разделе обобщаются сведения об операциях, кото-рые можно применять только к объектам определенных типов.