4.2 Связывание.

   


Назад  |  Вперёд

Если явно не определено иначе, то имя, не являющееся локальным для некоторой функции или класса, должно обозначать один и тот же тип, значение, функцию или объект во всех единицах трансляции данной программы. Иными словами, в программе может быть только один нелокальный тип, значение, функция или объект с данным именем. Рассмотрим для примера два файла:

           // file1.c
              int a = 1;
              int f() { /* какие-то операторы */ }

           // file2.c
              extern int a;
              int f();
              void g() { a = f(); }

В функции g() используются те самые a и f(), которые определены в файле file1.c. Служебное слово extern показывает, что описание a в файле file2.c является только описанием, но не определением. Если бы присутствовала инициализация a, то extern просто проигнорировалось бы, поскольку описание с инициализацией всегда считается определением. Любой объект в программе может определяться только один раз. Описываться же он может неоднократно, но все описания должны быть согласованы по типу. Например:

           // file1.c:
              int a = 1;
              int b = 1;
              extern int c;

           // file2.c:
              int a;
              extern double b;
              extern int c;

Здесь содержится три ошибки: переменная a определена дважды ("int a;" - это определение, означающее "int a=0;"); b описано дважды, причем с разными типами; c описано дважды, но неопределено. Такие ошибки (ошибки связывания) транслятор, который обрабатывает файлы по отдельности, обнаружить не может, но большая их часть обнаруживается редактором связей.

Следующая программа допустима в С, но не в С++:

           // file1.c:
              int a;
              int f() { return a; }

           // file2.c:
              int a;
              int g() { return f(); }

Во-первых, ошибкой является вызов f() в file2.c, поскольку в этом файле f() не описана. Во-вторых, файлы программы не могут быть правильно связаны, поскольку a определено дважды.

Если имя описано как static, оно становится локальном в этом файле. Например:

           // file1.c:
              static int a = 6;
              static int f() { /* ... */ }

           // file2.c:
              static int a = 7;
              static int f() { /* ... */ }

Приведенная программа правильна, поскольку a и f определены как статические. В каждом файле своя переменная a и функция f().

Если переменные и функции в данной части программы описаны как static, то в этой части программы проще разобраться, поскольку не нужно заглядывать в другие части. Описывать функции как статические полезно еще и по той причине, что транслятору предоставляется возможность создать более простой вариант операции вызова функции. Если имя объекта или функции локально в данном файле, то говорят, что объект подлежит внутреннему связыванию. Обратно, если имя объекта или функции нелокально в данном файле, то он подлежит внешнему связыванию.

Обычно говорят, что имена типов, т.е. классов и перечислений, не подлежат связыванию. Имена глобальных классов и перечислений должны быть уникальными во всей программе и иметь единственное определение. Поэтому, если есть два даже идентичных определения одного класса, это - все равно ошибка:

           // file1.c:
              struct S { int a; char b; };
              extern void f(S*);

           // file2.c:
              struct S { int a; char b; };
              void f(S* p) { /* ... */ }

Но будьте осторожны: опознать идентичность двух описаний класса не в состоянии большинство систем программирования С++. Такое дублирование может вызвать довольно тонкие ошибки (ведь классы в разных файлах будут считаться различными).

Глобальные функции-подстановки подлежат внутреннему связыванию, и то же по умолчанию справедливо для констант. Синонимы типов, т.е. имена typedef, локальны в своем файле, поэтому описания в двух данных ниже файлах не противоречат друг другу:

           // file1.c:
              typedef int T;
              const int a = 7;
              inline T f(int i) { return i+a; }

           // file2.c:
              typedef void T;
              const int a = 8;
              inline T f(double d) { cout<<d; }

Константа может получить внешнее связывание только с помощью явного описания:

           // file3.c:
              extern const int a;
              const int a = 77;

           // file4.c:
              extern const int a;
              void g() { cout<<a; }

В этом примере g() напечатает 77.


Назад  |  Вперёд


OtDiatlovaOU
К началу книги

Оформление и дизайн книги OtDiatlovaOU.
Вся книга, архив.