Классы хранения и видимость переменных

 

Общие положения

 

Для каждого идентификатора существует как минимум два атрибута: тип и класс хранения. Тип определяет размер памяти, выделяемой компилятором для объекта и способ интерпретации выделенной памяти (например, как адрес памяти для указателей или массив однотипных элементов). Класс хранения определяет место, где объект располагается (внутренние регистры процессора, сегмент данных, сегмент стека), и одновременно время жизни объекта (например, все время выполнения программы или только время выполнения конкретной части кода программы). Класс хранения либо задается явно, либо компилятор «сам» определяет это по местоположению описания в тексте программы.

Время жизни и класс хранения тесно связаны друг с другом. С точки зрения времени жизни различают три типа объектов: статические, локальные, динамические.

Тесно связанным с понятием «класс хранения» является и понятие видимости, или области определения идентификатора. Область определения - это та часть программы, в пределах которой идентификатор может использоваться для доступа. Есть несколько типов области определения: в пределах блока (локальная видимость); в пределах функции; в пределах файла текста программы.

 

Область определения и видимость идентификатора.

 

Внешние переменные

 

Текст программы на С++ может быть размещен целиком в одном текстовом файле, в котором будут помещены функция 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;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.012 сек.