Автоматические переменные
Переменные, декларированные внутри функций, являются внутренними и называются локальными переменными. Никакая другая функция не имеет прямого доступа к ним. Такие объекты существуют временно на этапе активности функции.
Каждая локальная переменная существует только в блоке кода, в котором она объявлена, и уничтожается при выходе из него. Эти переменные называют автоматическими и располагаются в стековой области памяти.
По умолчанию, локальные объекты, объявленные в теле функции, имеют атрибут класса памяти auto.
Принадлежность к этому классу можно также подчеркнуть явно с помощью ключевого слова auto. Например:
void main(void) {
auto int max, lin;
... }
так поступают, если хотят показать, что определение переменной не нужно искать вне функции.
Регистровая память (атрибут register) - объекты целого типа и символы рекомендуется разместить не в оперативной памяти, а в регистрах общего назначения (процессора), а при нехватке регистров - в стековой памяти (размер объекта не должен превышать разрядности регистра), для других типов компилятор может использовать другие способы размещения, а может просто проигнорировать данную рекомендацию.
Регистровая память позволяет увеличить быстродействие программы, но к размещаемым в ней объектам в языке Си (но не С++) не применима операция адресации &, а также это неприменимо для массивов, структур, объединений и переменных с плавающей точкой.
Внешние переменные
Объекты, размещаемые в статической памяти, декларируются с атрибутом static и могут иметь любой атрибут области действия. Глобальные объекты всегда являются статическими. Атрибут static, использованный при описании глобального объекта, предписывает ограничение его области применимости только в пределах остатка текущего файла. Значения локальных статических объектов сохраняются при повторном вызове функции. Таким образом, в языке С ключевое слово static имеет разный смысл для локальных и глобальных объектов.
Переменная, описанная вне функции, является внешней переменной. Их еще называют глобальными переменными.
Так как внешние переменные доступны всюду, их можно использовать вместо списка аргументов для передачи значений между функциями.
Внешние переменные существуют постоянно. Они сохраняют свои значения и после того, как функции, присвоившие им эти значения, завершат свою работу.
При отсутствии явной инициализации для внешних и статических переменных гарантируется их обнуление, автоматические и регистровые переменные имеют неопределенные начальные значения («мусор»).
Внешняя переменная должна быть определена вне всех функций. При этом ей выделяется фактическое место в памяти. Такая переменная должна быть описана в каждой функции, которая собирается ее использовать. Это можно сделать либо явным описанием extern, либо по контексту.
Чтобы функция могла использовать внешнюю переменную, ей нужно сообщить идентификатор этой переменной. Один из способов - включить в функцию описание extern.
В определенных ситуациях описание extern может быть опущено: если внешнее определение переменной находится в том же файле, раньше ее использования в некоторой конкретной функции, то не обязательно включать описание extern для этой переменной в саму функцию. Фактически, обычная практика заключается в помещении определений всех переменных в начале исходного файла и последующем опусканием всех описаний extern.
Включение ключевого слова extern позволяет функции использовать внешнюю переменную, даже если она определяется позже в этом или другом файле.
Важно различать описание внешней переменной и ее определение. Описание указывает свойство переменной, ее размер, тип и т. д.; определение же вызывает еще и отведение памяти. Например, если вне какой-либо функции появляются строчки:
int sp;
double val[20];
то они определяют внешние переменные sp и val, вызывают отведение памяти для них и служат в качестве описания для остальной части этого исходного файла. В то же время строчки:
extern int sp;
extern double val [];
описывают в остальной части этого исходного файла переменную sp как int, а vol как массив типа double, но не создают переменных и не отводят им места в памяти.
Во всех файлах, составляющих исходную программу, должно содержаться только одно определение внешней переменной. Другие файлы могут содержать описание extern для доступа к ней.
Любая инициализация внешней переменной проводится только в декларации. В декларации должны указываться размеры массивов, а в описании extern этого можно не делать.
Например, в файле 1:
int sp = 0;
double val [20];
...
в файле 2:
extern int sp;
extern double val [];
...
В Си есть возможность с помощью #include иметь во всей программе только одну копию описаний extern и вставлять ее в каждый файл во время его компиляции.
Если переменная с таким же идентификатором как внешняя декларирована в функции без указания extern, то тем самым она становится внутренней в данной функции.
Не стоит злоупотреблять внешними переменными. Такой стиль программирования приводит к программам, связи данных внутри которых не вполне очевидны. Переменные при этом могут изменяться неожиданным образом. Программу становится трудно модифицировать.
Файл 1: | Файл 2: |
int x, y; char str[ ] = “Rezult = ”; void main(void) { . . . } void fun1(void) { y = 15; cout << str << y; } | extern int x, y; extern char str[ ]; int r, q; void fun2(void) { x = y / 5; cout << str << x; } void fun3(void) { int z= x + y; cout << str << z; } |
16.4. Область действия переменных
В языке С нет ключевого слова, указывающего область действия объекта. Область действия определяется местоположением декларации объекта в файле исходного текста программы (любой объект полностью описывается в одном операторе).
Напомним общую структуру исходного текста программ на языке С:
<директивы препроцессора>
<описание глобальных объектов>
<заголовок функции>
{
<описание локальных объектов>
<список инструкций>
}
Файл исходного текста может включать любое количество определений функций и/или глобальных данных.
Параметры функции являются локальными объектами и должны отличаться по идентификаторам от используемых в коде функции глобальных объектов. Локальные объекты, описанные в коде функции, имеют приоритет перед объектами, описанными вне функции, например:
. . .
int n; // глобальное n
void main (void) {
int i;
...
f1(i);
...
f2(n); // локальное n
}
f1(int i) {
...
i=n; // глобальное n
...
}
f2(int n) {
int i;
...
i=n; // локальное n
...
}
В любом месте файла исходного текста можно ссылаться на глобальные объекты, определенные ниже в остатке текущего файла или в других файлах. Для этого необходимо описать тип объекта и добавить к описанию ключевое слово extern. Описания функций подразумевают атрибут extern по умолчанию. Разрешается опускать длину внешних одномерных массивов, но операция sizeof к таким массивам становится бессмысленной.
Следует учитывать, что любая декларация объекта действует только на остаток файла исходного текста.
В С++ допускается в разных блоках программы использовать один и тот же идентификатор объекта, тогда более внутреннее объявление объекта скрывает доступ к объекту на более высоком уровне декларации.
void main(void) {
int i = 3;
cout << “\n Block 1 - “ << i ;
{
float i = 2.5;
cout << “\n Block 2 - “ << i ;
{
char i = ‘a’;
cout << “\n Block 3 - “ << i ;
}
}
cout << “\n New Block 1 - “ << ++i ;
getch();
}
В результате выполнения этой программы, на экране получим:
Block 1 - 3
Block 2 - 2.5
Block 3 - a
New Block 1 - 4