// определение находится в другом файле
void calc(int);
int main()
{
int loc1 = get(); // ошибка: get() не объявлена
calc(loc1); // правильно: calc() объявлена
// ...
}
Определение объекта имеет две формы:
type_specifier object_name;
type_specifier object_name = initializer;
Вот, например, определение obj1. Здесь obj1 инициализируется значением 97:
int obj1 = 97;
Следующая инструкция задает obj2, хотя начальное значение не задано:
int obj2;
Объект, определенный в глобальной области видимости без явной инициализации, гарантированно получит нулевое значение. Таким образом, в следующих двух примерах и var1, и var2 будут равны нулю:
int var1 = 0;
int var2;
Глобальный объект можно определить в программе только один раз. Поскольку он должен быть объявлен в исходном файле перед использованием, то для программы, состоящей из нескольких файлов, необходима возможность объявить объект, не определяя его. Как это сделать?
С помощью ключевого слова extern, аналогичного объявлению функции: оно указывает, что объект определен в другом месте – в этом же исходном файле или в другом. Например:
extern int i;
Эта инструкция “обещает”, что в программе имеется определение, подобное
int i;
extern-объявление не выделяет места под объект. Оно может встретиться несколько раз в одном и том же исходном файле или в разных файлах одной программы. Однако обычно находится в общедоступном заголовочном файле, который включается в те модули, где необходимо использовать глобальный объект:
// заголовочный файл
extern int obj1;
extern int obj2;
// исходный файл
int obj1 = 97;
int obj2;
Объявление глобального объекта с указанием ключевого слова extern и с явной инициализацией считается определением. Под этот объект выделяется память, и другие определения не допускаются:
extern const double pi = 3.1416; // определение
const double pi; // ошибка: повторное определение pi
Ключевое слово extern может быть указано и при объявлении функции – для явного обозначения его подразумеваемого смысла: “определено в другом месте”. Например:
extern void putValues( int*, int );
8.2.2. Сопоставление объявлений в разных файлах
Одна из проблем, вытекающих из возможности объявлять объект или функцию в разных файлах, – вероятность несоответствия объявлений или их расхождения в связи с модификацией программы. В С++ имеются средства, помогающие обнаружить такие различия.
Предположим, что в файле token.C функция addToken() определена как имеющая один параметр типа unsigned char. В файле lex.C, где эта функция вызывается, в ее определении указан параметр типа char.
// ---- в файле token.C ----
int addToken( unsigned char tok ) { /* ... */ }
// ---- в файле lex.C ----
extern int addToken( char );
Вызов addToken() в файле lex.C вызывает ошибку во время связывания программы. Если бы такое связывание прошло успешно, можно представить дальнейшее развитие событий: скомпилированная программа была протестирована на рабочей станции Sun Sparc, а затем перенесена на IBM 390. Первый же запуск потерпел неудачу: даже самые простые тесты не проходили. Что случилось?
Вот часть объявлений набора лексем:
const unsigned char INLINE = 128;
const unsigned char VIRTUAL = 129;
Вызов addToken() выглядит так:
curTok = INLINE;
// ...
addToken( curTok );
Тип char реализован как знаковый в одном случае и как беззнаковый в другом. Неверное объявление addToken() приводит к переполнению на той машине, где тип char является знаковым, всякий раз, когда используется лексема со значением больше 127. Если бы такой программный код компилировался и связывался без ошибки, во время выполнения могли обнаружиться серьезные последствия.
В С++ информация о количестве и типах параметров функций помещается в имя функции – это называется безопасным связыванием (type-safe linkage). Оно помогает обнаружить расхождения в объявлениях функций в разных файлах. Поскольку типы параметров unsigned char и char различны, в соответствии с принципом безопасного связывания функция addToken(), объявленная в файле lex.C, будет считаться неизвестной. Согласно стандарту определение в файле token.C задает другую функцию.