Классы хранения и видимость переменных
Общие положения
Для каждого идентификатора существует как минимум два атрибута: тип и класс хранения. Тип определяет размер памяти, выделяемой компилятором для объекта и способ интерпретации выделенной памяти (например, как адрес памяти для указателей или массив однотипных элементов). Класс хранения определяет место, где объект располагается (внутренние регистры процессора, сегмент данных, сегмент стека), и одновременно время жизни объекта (например, все время выполнения программы или только время выполнения конкретной части кода программы). Класс хранения либо задается явно, либо компилятор «сам» определяет это по местоположению описания в тексте программы.
Время жизни и класс хранения тесно связаны друг с другом. С точки зрения времени жизни различают три типа объектов: статические, локальные, динамические.
Тесно связанным с понятием «класс хранения» является и понятие видимости, или области определения идентификатора. Область определения - это та часть программы, в пределах которой идентификатор может использоваться для доступа. Есть несколько типов области определения: в пределах блока (локальная видимость); в пределах функции; в пределах файла текста программы.
Область определения и видимость идентификатора.
Внешние переменные
Текст программы на С++ может быть размещен целиком в одном текстовом файле, в котором будут помещены функция main() и все другие функции, если они есть. Однако это не всегда удобно (а иногда и просто невозможно). В таком случае текст программы размещается в нескольких текстовых файлах, каждый из которых содержит целиком одну или несколько функций. Для объединения в одну программу эти текстовые файлы компилируются совместно. Информация (имя текстового файла, директорий) обо всех объединяемых в одну программу файлах, может быть помещена в так называемый файл проекта. Компилятор порождает для каждого исходного текстового файла отдельный объектный файл. Затем эти файлы объединяются компоновщиком в загрузочный модуль (.EXE-файл).
Объектный файл - это неготовый к исполнению программный код. Образно говоря, объектный файл - это отдельный модуль дорогого музыкального центра, состоящего из отдельных блоков: усилителя, радиоприемника, магнитофона, проигрывателя. Для того, чтобы «заиграла» музыка, необходимо эти блоки соединить проводами друг с другом. Чтобы не ошибиться, разъемы на разных сторонах проводов часто делают неодинаковыми. Разъемы в объектных файлах порождает компилятор. Один вид разъемов соответствует известному в языке ассемблера понятию PUBLIC, другой - понятию EXTERN. Процесс объединения блоков с помощью проводов похож на работу компоновщика. В роли разъемов, объединяемых в объектных файлах проводами, выступают так называемые внешние ссылки. Это либо имена функций, либо переменные. Встретив разъем EXTERN, компоновщик ищет подходящий ему разъем PUBLIC сначала в данном объектном файле, затем в других объектных файлах из числа компилируемых совместно. Потом он просматривает библиотеки. Если подходящий разъем не найден, компоновка завершается с сообщением об ошибке типа «Неразрешенная внешняя ссылка» или «неизвестное имя ...» (Unresolved external …).
Если разъем EXTERN соответствует имени библиотечной функции, то она помещается в загрузочный модуль. Другими словами, разъем EXTERN - это указание компоновщику искать соответствующий ему по имени разъем типа PUBLIC.
Область определения идентификатора нужна компилятору для того, чтобы сгенерировать корректный машинный код. Если идентификатор описан внутри блока {}, он имеет локальную область определения, ограниченную размерами блока. Отсюда следует, что все переменные, описанные внутри функции, в том числе и формальные параметры, имеют локальную область определения. Для локальных переменных компилятор не создает никаких разъемов – ни PUBLIC, ни EXTERN. Если идентификатор описан вне каких-либо блоков, он является глобальным и имеет область определения, начинающуюся с точки описания и продолжающуюся до конца файла. Для всех глобальных переменных компилятор создает разъем PUBLIC. Глобальный идентификатор будет видимым из всех функций ниже точки описания переменной. Чтобы идентификатор стал видимым в функциях выше точки описания переменной или в функциях, определения которых расположены в других файлах, используется объявление с атрибутом EXTERN. Обратите внимание на то, что воздействие на видимость объекта не связано с резервированием памяти компилятором. Поэтому, например, строка программы
extern int var1;
является не описанием переменной, а объявлением ее как внешней ссылки. Обрабатывая такое предложение, компилятор создает разъем EXTERN для переменной var1. Отсюда вытекают два очевидных следствия:
при объявлении переменной как EXTERN просто невозможно выполнить ее инициализацию. Например, выражение
extern int var1 = 20;
является ошибкой;
для того чтобы какой-либо объект данных, в том числе и объявленный как EXTERN, был видим из всех функций файла, необходимо поместить описание или объявление объекта в самом начале файла, перед первой функцией.
Сначала компоновщик просматривает глобальные переменные текущего файла (т.е. все разъемы PUBLIC в том файле, в котором встретилось объявление EXTERN). Если не найден подходящий глобальный идентификатор в текущем файле, компоновщик просматривает все идентификаторы, помеченные как PUBLIC, в других объектных файлах, образующих один проект. Если и там не будет найден соответствующий идентификатор, поиск продолжается в библиотеках. Внешние переменные – один из способов «передачи» функциям аргументов и «возврата» из функций необходимых значений. Просто взаимодействующие функции используют общие области памяти.
Имена функций в языке С++ – это всегда глобальные имена, видимые по умолчанию из всех файлов одного проекта. Однако прототип функции действует только в пределах одного файла. Из-за этого, в частности, приходится помещать во все совместно компилируемые файлы директивы препроцессора, связанные с подключением .h-файлов, содержащих прототипы библиотечных функций. Приведем пример программы, текст которой размещен в двух файлах (для связи функций программы используется внешняя переменная i):
// Prim7_1.cpp
#include <stdio.h>
extern int i;
void main(void)
{
void next(void); /* прототип */
int i = 3;
i++;
printf("В main i = %d\n", i);
next();
}
void next(void)
{
void other(void); /* прототип */
i++;
printf("B next i = %d\n", i);
other();
}
// Prim7_2.cpp
#include <stdio.h>
extern int i;
void other(void)
{
i++;
printf("B other i = %d\n", i);
}
В результате выполнения программы получены следующие результаты:
B main i = 4
B next i = 5
B other i = 6
Дата добавления: 2017-01-29; просмотров: 865;