Лекция 9.
Время жизни и область видимости.
Пространства имён. Компоновка
Вопросы
1. Время жизни и область
видимости. Спецификации класса памяти
3. Компоновка
1. Время жизни и область видимости. Спецификации класса памяти
Введение
Время жизни переменной может быть глобальным и локальным.
Переменная с глобальным временем жизни характеризуется тем, что в течение всего
времени выполнения программы с ней ассоциирована ячейка памяти и значение.
Переменной с локальным временем жизни выделяется новая ячейка памяти при каждом
входе в блок, в котором она определена или объявлена. Время жизни
функции всегда глобально.
Область видимости объекта
(переменной или функции) определяет, в каких участках программы допустимо
использование имени этого объекта.
Область видимости имени начинается в точке объявления,
точнее, сразу после объявителя, но перед инициализатором. Поэтому допускается
использование имени в качестве инициализирующего значения для себя самого.
int x = x; |
// Странно! |
Это допустимо, но не разумно.
Прежде чем имя может быть использовано в программе
на C++, оно должно быть объявлено (иметь объявление), т.е. должен
быть указан тип имени, чтобы компилятор знал, на сущность какого вида ссылается
имя. Определение не только связывает тип с именем, но и
определяет некоторую сущность, которая соответствует имени. В программе
на C++ для каждого имени должно быть ровно одно определение.
Объявлений же может быть несколько. Все объявления некой сущности должны
согласовываться по типу этой сущности.
int count; |
|
int count; |
// Ошибка – повторное определение |
extern int error_number; |
|
extern short error_number; |
// Ошибка –
несоответствие типов объявлений |
Объявления и определения, записанные внутри
какого-либо блока, называются внутренними или локальными.
Объявления и определения, записанные за пределами всех блоков, называются внешнимиили глобальными.
1.1. Переменные
Объявление переменной задает имя и атрибуты переменной. Определение переменной,
помимо этого, приводит к выделению для нее памяти. Кроме того,
определение переменной задает её начальное значение (явно или неявно).
Таким образом, не каждое объявление переменной является определением
переменной. К объявлениям, которые не являются определением
относятся объявления формальных параметров функций, а также объявления
со спецификацией класса памяти extern,
которые являются ссылкой на переменную, определённую в другом месте программы
(см. ниже).
Объявления переменных в языке C++ имеют
следующий синтаксис:
[<спецификация класса памяти>] <тип>
<описатель> [= <инициализатор>] [, <описатель> [= <инициализатор>]
...];
В языке C++ имеется четыре спецификации
класса памяти:
Спецификации класса памяти auto и register могут быть использованы только на
внутреннем уровне.
1.1.1.
Глобальные переменные
Переменная, объявленная на внешнем уровне, имеет
глобальное время жизни. При отсутствии инициализатора такая переменная
инициализируется нулевым значением. Область видимости переменной, определенной
на внешнем уровне, распространяется от точки, где она определена, до конца
исходного файла. Переменная недоступна выше своего определения в том же самом
исходном файле. На другие исходные файлы программы область видимости переменной
может быть распространена только в том случае, если ее определение не содержит
спецификации класса памяти static.
Если в объявлении переменной задана спецификация
класса памяти static, то в других
исходных файлах могут быть определены другие переменные с тем же именем и любым
классом памяти. Эти переменные никак не буду связаны между собой.
Спецификация класса памяти extern используется
для объявления переменной, определенной где-то в другом месте программы. Такие
объявления используются в случае, когда нужно распространить на данный исходный
файл область видимости переменной, определенной в другом исходном файле
на внешнем уровне. Область видимости переменной
распространяется от места объявления до конца исходного файла. В объявлениях,
которые используют спецификацию класса памяти extern,
инициализация не допускается, так как они ссылаются на переменные, значения
которых определены в другом месте.
1.1.2.
Локальные переменные
Переменная, объявленная на внутреннем уровне, доступна
только в том, блоке в котором она объявлена, независимо от класса памяти.
По умолчанию она имеет класс памяти auto.
Переменные этого класса размещаются в стеке. Переменные класса памяти auto автоматически не инициализируются, поэтому
в случае отсутствия инициализации в объявлении значение переменной класса
памяти auto считается
неопределенным.
Спецификация класса памяти register требует,
чтобы переменной была выделена память в регистре, если это возможно. Т.к.
работа с регистрами происходит быстрее, спецификация класса памяти registerобычно используется
для переменных, к которым предполагается обращаться очень часто.
Для каждого рекурсивного входа в блок порождается
новый набор переменных класса памяти auto и register. При этом каждый раз производится
инициализация переменных, в объявлении которых заданы инициализаторы.
Если переменная, объявленная на внутреннем уровне,
имеет спецификацию памяти static, то
область видимости остается прежней, а время жизни становится глобальным. В
отличие от переменных класса памяти auto,
переменные, объявленные со спецификацией класса памяти static,
сохраняют свое значение при выходе из блока. Переменные класса памяти static могут быть инициализированы константным
выражением. Если явной инициализации нет, то переменная класса памяти static автоматически инициализируется нулевым
значением. Инициализация выполняется один раз во время компиляции и не
повторяется при каждом входе в бока. Все рекурсивные вызовы данного бока будут
разделять единственный экземпляр переменной класса памяти static.
Переменная, объявленная со спецификацией класса памяти extern, является ссылкой на переменную с тем же
самым именем, определенную на внешнем уровне в любом исходном
файле программы. Цель внутреннего объявления extern состоит
в том, чтобы сделать определение переменной доступным именно внутри данного
блока.
Файл 1 |
Файл 2 |
||
int a, b; static
int c; extern
int d int m[10]; int f(int x) { int y; static
int z; static
int c; ...
} |
// Глобальные переменные // На переменную с
нельзя будет // сослаться из другого файла // Нельзя использовать инициализатор с extern // Переменная класса auto. // Локальные время жизни и область
видимости. // Локальная переменная класса static.
// Имеет глобальное время жизни. // Локальная статическая переменная c
// скрывает глобальную переменную c |
extern int a; static
double c; int d = 10; extern
int m[]; int g(int x) { extern
int b; register int w; ...
} |
// Ссылка на переменную
а из файла 1 // Но можно объявить переменную // с таким же именем c в другом
файле // С extern
можно опускать размер массива // Нельзя использовать auto
и register // на внешнем уровне // Ссылка на переменную b из файла
1, // которая будет доступна только в функции g // Переменная класса register |
1.1.3. Сокрытие
имён
Объявление имени в блоке может скрыть объявление этого
имени в охватывающем блоке или глобальное имя. То есть имя может быть замещено
внутри блока и будет ссылаться там на другую сущность. После выхода из блока
имя восстанавливает свой прежний смысл. К скрытому глобальному имени можно
обратиться с помощью операции разрешения области видимости ::. Скрытое
имя члена класса можно использовать, квалифицировав его именем класса. Скрытое
глобальное имя можно использовать, если квалифицировать его унарной операцией
разрешения области видимости.
int x; |
|
void f() |
|
{ double x = 0; |
// Глобальная переменная х скрыта |
::x = 2; |
// Присваивание
глобальной переменной х |
x = 2.5; |
// Присваивание
локальной переменной х |
} |
|
int f(int x) { ... } |
|
class X |
|
{ public: |
|
static int f() { ... } |
|
}; |
|
int ff() |
|
{ return X::f(); } |
// Вызов функции f класса Х, а не глобальной функции f |
Не существует способа обращений к скрытой локальной
переменной.
1.1.4.
Объявления в условиях и цикле for
Во избежание случайного неправильного использования переменных
их лучше вводить в наименьшей возможной области видимости. В частности,
локальную переменную лучше объявлять в тот момент, когда ей надо присвоить
значение. В этом случае исключаются попытки использования переменной до момента
её инициализации.
Одним из самых элегантных применений этих идей
является объявление переменной в условии. Рассмотрим
пример.
if (double
d = f(x)) y /= d; |
Область видимости переменной d простирается
от точки её объявления до конца оператора, контролируемого условием. Если бы в
инструкции if была ветвь else, то областью видимости переменной d были
бы обе ветви.
Очевидной и традиционной альтернативой является объявление
переменной до условия, но в этом случае область видимости началась бы до мести
использования переменной и продолжалась бы после завершения её «сознательной»
жизни.
double d; ... d2 = d; ... |
|
if (d =
f(x))
y /= d; ... |
// Внимание!!! |
d = 2.0; |
// Два несвязанных
использования переменной d |
Объявление переменных в условиях, кроме того, что даёт
логические преимущества, приводит также к более компактному исходному коду.
Объявление в условии должно объявлять и инициализировать
единственную переменную или константу.
Переменную можно также объявить в инициализирующей части оператора for
. В этом случае область видимости переменной (или переменных) простирается до конца оператора.Если
требуется узнать значение индекса после выхода из цикла, переменную надо
объявить вне его.
[<спецификация
класса памяти>] <тип> <имя> (<список формальных
параметров>);
[<спецификация
класса памяти>] <тип> <имя> (<список формальных
параметров>)
Функции
имеют глобальное время жизни.
Пространство
имён объявляется следующим образом:
namespace <имя пространства имён>
{ <объявления и определения> }
Такие
синонимы следует делать как можно более локальными во избежание конфликтов
имён.
Единственное
объявление using делает видимыми все версии перегруженной функции.
Директива using делает доступными все имена из пространства
имён.
2.4.
Неименованные пространства имён
2.6.
Псевдонимы пространств имён
Это
может значительно облегчить проблему смены версии библиотеки.
В
этом примере содержатся три ошибки:
Практическое
правило гласит, что заголовочный файл может содержать:
3.3.
Правило одного определения
1.
они
находятся в различных единицах компиляции;
2.
они
идентичны лексема за лексемой;
3.
значения
лексем одинаковы в обеих единицах компиляции.
Приведём
примеры трёх способов нарушения правила ODR.
Есть
два возможных решения этой проблемы:
Возможен
другой способ использования стражей включения.
Второй
вариант немного быстрее, зато первый вариант является самодостаточным.