Бьерн Страуструп - Язык программирования С++. Главы 2-4
Страница 51. Связывание


 

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.

 
« Предыдущая статья   Следующая статья »