Основы программирования на языке C

         

Классы памяти и область действия


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

/* глобальная переменная ext */ int ext; /* внешняя переменная */ main( ) { extern int ext; printf("Сколько курсов на сайте intuit.ru?\n"); scanf("%d",&ext); while(ext != 30) critic( ); printf("Посмотрите на сайте!\n"); } critic( ) { extern int ext; printf("Ошибка. Попытайтесь снова.\n"); scanf("%d",&ext); }

Результат:

Сколько курсов на сайте intuit.ru? 20 Ошибка. Попытайтесь снова. 30 Посмотрите на сайте!

Мы сделали переменную ext внешней, описав ее вне любого определения функции. Внутри функции, использующей эту переменную, мы объявляем ее внешней при помощи ключевого слова extern, предшествующего спецификации типа переменной. Компилятор ищет определение этой переменной вне функции. Если бы мы опустили ключевое слово extern в функции critic( ), то компилятор создал бы в функции critic новую переменную и тоже назвал бы ее ext. Тогда другая переменная ext, которая находится в main( ), никогда не получила бы нового значения.

Каждая переменная имеет тип и принадлежит к некоторому классу памяти. Время жизни и область действия идентификатора определяются ассоциированным с ним классом памяти. Существуют четыре разновидности классов памяти:

auto - автоматический - локальные идентификаторы, память для которых выделяется при входе в блок, т.е. составной оператор, и освобождается при выходе из блока. Слово auto является сокращением слова automatic.

static - статический - локальные идентификаторы, существующие в процессе всех выполнений блока. В отличие от идентификаторов типа auto, для идентификаторов типа static память выделяется только один раз - в начале выполнения программы, и они существуют, пока программа выполняется.

extern - внешний - идентификаторы, называемые внешними, external, используются для связи между функциями, в том числе независимо скомпилированными функциями, которые могут находиться в различных файлах. Память, ассоциированная с этими идентификаторами, является постоянной, однако ее содержимое может меняться. Эти идентификаторы описываются вне функции.

register - регистровый - идентификаторы, подобные идентификаторам типа auto. Их значения, если это возможно, должны помещаться в регистрах машины для обеспечения быстрого доступа к данным.

Если класс памяти идентификатора не указан явно, то его класс памяти задается положением его определения в тексте программы. Если идентификатор определяется внутри функции, тогда его класс памяти auto, в остальных случаях идентификатор имеет класс памяти extern.

Предположим, что имеется программа на языке Си, исходный текст которой содержится в нескольких файлах. Для разделения данных (для связи) в функциях в этих файлах используются идентификаторы, определенные как extern. Если функция ссылается на внешний идентификатор, то файл, содержащий его, должен иметь описание или определение этого идентификатора. Явное задание класса памяти extern указывает на то, что этот идентификатор определен в другом файле, и здесь ему память не выделяется, а его описание дано лишь для проверки типа и для генерации кода.

!При описании внешних массивов максимальное значение первого индекса массива указывать нет необходимости. Оно будет получено из соответствующего определения, что создает дополнительные удобства для пользователя.


Для внешнего идентификатора память выделяется только в том случае, если класс памяти не указан явно.

!Явное указание памяти extern является отличительным признаком внешнего описания от внешнего определения.
Хотя описание внешнего идентификатора может встретиться во многих файлах, только один файл должен содержать определение внешнего идентификатора. Область действия внешних идентификаторов не ограничивается файлом, содержащим их определения, а включает также файлы с соответствующими описаниями, с классом памяти extern.

Определение класса памяти переменной зависит от того, где переменная описана и какое ключевое слово, если оно есть, используется.

Класс памяти позволяет установить два факта. Во-первых, определить, какие функции имеют доступ к переменной. Пределы, до которых переменная доступна, характеризуют ее область действия. Во-вторых, определить, как долго переменная находится в памяти. Теперь подробнее рассмотрим свойства каждого типа.


Регистровые переменные


Обычно переменные хранятся в памяти машины. Регистровые переменные запоминаются в регистрах центрального процессора, где доступ к ним и работа с ними выполняются гораздо быстрее, чем в памяти. В остальном регистровые переменные аналогичны автоматическим переменным.

Пример:

main( ) { register int pleat; }

Компилятор сравнивает наши требования с количеством доступных регистров, поэтому мы можем и не получить то, что хотим. В этом случае переменная становится простой автоматической переменной.

Особенности работы с языком Си. Какой класс памяти применять? Ответ на вопрос - автоматический. Этот класс памяти выбран по умолчанию. Использование внешних переменных очень соблазнительно. Если описать все переменные как внешние, то не будет забот при использовании аргументов и указателей для связи между функциями в прямом и обратном направлениях. Но тогда возникает проблема с функцией С, изменяющей переменные в функции А, а мы этого не хотели! Такая проблема значительно перевешивает кажущуюся привлекательность широкого использования внешних переменных. Одно из золотых правил программирования заключается в соблюдении принципа "необходимо знать только то, что нужно". Организуйте работу каждой функции автономно, насколько это возможно, и используйте глобальные переменные только тогда, когда это действительно необходимо!

Операция получения адреса & неприменима к регистровым переменным. Любые переменные в блоке, кроме формальных параметров функции, могут быть определены как статические.

Подведем итог.

Классы памяти, которые описываются внутри функции:

автоматический, продолжительность существования - временно, область действия - локальная;регистровый, продолжительность существования - временно, область действия - локальная;статический, продолжительность существования - постоянно, область действия - локальная.

Классы памяти, которые определяются вне функции:

внешний, продолжительность существования - постоянно, область действия глобальная (все файлы);внешний статический, продолжительность существования - постоянно, область действия - глобальная (один файл).



Статические переменные


Статические здесь означает, что переменные остаются в работе. Они имеют такую же область действия, как автоматические переменные, но они не исчезают, когда содержащая их функция закончит свою работу. Компилятор хранит их значения от одного вызова функции до другого.

Пример:

/* Статическая переменная */ main( ) { int count for(count = 1;count <= 3; count ++) { printf("Подсчет студентов %d:\n", count); man_woman ( ); } } man_woman( ) { int man = 1; static int woman = 1; printf("юношей = %d и девушек = %d\n", man++, woman++); }

Функция man_woman увеличивает каждую переменную после печати ее значения. Работа этой программы дает следующие результаты:



Подсчет студентов 1: юношей = 1 и девушек = 1 Подсчет студентов 2: юношей = 1 и девушек = 2 Подсчет студентов 3: юношей = 1 и девушек = 3

Статическая переменная woman помнит, что ее значение было увеличено на 1, в то время как для переменной man начальное значение устанавливается каждый раз заново. Это указывает на разницу в инициализации: man инициализируется каждый раз, когда вызывается man_woman ( ), в то время как woman инициализируется только один раз при компиляции функции man_woman ( ).



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


Переменная, описанная вне функции, является внешней.

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

Пример:

int global_flag;

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

Если слово extern не включено в описание внутри функции, то под этим именем создается новая автоматическая переменная. Мы можем пометить вторую переменную как автоматическую с помощью слова auto.



Внешние статические переменные


Можно описать статические переменные вне любой функции. Это создает внешнюю статическую переменную. Разница между внешней переменной и внешней статической переменной заключается в области их действия. Обычная внешняя переменная может использоваться функциями в любом файле, а внешняя статическая переменная может использоваться только функциями того же самого файла, причем после определения переменной. Статическую переменную мы описываем вне любой функции.