C. Бочков - Язык программирования Си для персонального компьютера
Возможно наличие в одном исходном файле на внешнем уровне нескольких объявлений переменной с одним и тем же именем. Следующая таблица позволяет определить реакцию компилятора языка Си в различных ситуациях изменения спецификации класса памяти в объявлении. Слово "пусто" в таблице означает ситуацию отсутствия спецификации класса памяти. Очевидно, что компилятор СП MSC строже ограничивает возможность переопределения класса памяти переменной.
Класс 1 Класс 2 СП TC СП MSC extern static static static static пусто static ошибка static extern static static пусто static static ошибкаПример: /* ИСХОДНЫЙ ФАЙЛ 1 */
/* объявление i, ссылающееся на данное ниже определение i */
extern int i;
main()
{
i = i + 1;
printf("%dn", i); /* значение i равно 4 */
next();
}
int i = 3; /* определение i */
next()
{
printf("%dn", i); /* значение i равно 5 */
other());
}
/* ИСХОДНЫЙ ФАЙЛ 2 */
/* объявление i, ссылающееся на определение i в первом исходном файле */
extern int i;
other()
{
i = i + 1;
printf("%dn", i); /* значение i равно 6 */
}
Два исходных файла в совокупности содержат три внешних объявления i. Только в одном объявлении содержится инициализация:
int i = 3; — глобальная переменная i определена с начальным значением 3.
Самое первое объявление extern в первом исходном файле делает глобальную переменную i доступной прежде ее определения в файле. Без этого объявления функция main не могла бы использовать глобальную переменную i. Объявление переменной i во втором исходном файле делает глобальную переменную i доступной во втором исходном файле.
Все три функции выполняют одно и то же действие: увеличивают i на 1 и печатают полученное значение. Значения распечатываются с помощью стандартной библиотечной функции printf. Печатаются значения 4, 5 и 6.
Если бы переменная i не была инициализирована ни в одном из объявлений, она была оы неявно инициализирована нулевым значением при компоновке. В этом случае программа напечатала бы значения 1, 2 и 3.
Объявление переменной на внутреннем уровне
Любая из четырех спецификаций класса памяти может быть использована для объявления переменной на внутреннем уровне. Если спецификация класса памяти опущена в объявлении переменной на внутреннем уровне, то подразумевается класс памяти auto. Как правило, ключевое слово auto опускается. Понятия объявления и определения для переменных внутреннего уровня совпадают, если только в объявлении не задана спецификация класса памяти extern.
Спецификация класса памяти auto объявляет переменную с локальным временем жизни. Область действия переменной распространяется на блок, в котором она объявлена, (и на все вложенные в него блоки). Переменные класса памяти auto автоматически не инициализируются, поэтому в случае отсутствия инициализации в объявлении значение переменной класса памяти auto считается неопределенным. Память под переменные класса памяти auto отводится в стеке.
Спецификация класса памяти register требует, чтобы компилятор языка Си выделил переменной память в регистре микропроцессора, если это возможно. Использование регистровой памяти обычно ускоряет доступ к переменной и уменьшает размер выполняемого кода программы. Переменные, объявленные с классом памяти register, имеют ту же самую область действия, что и переменные auto.
Число регистров, которое может быть использовано для хранения переменных, зависит от компьютера и от реализации компилятора языка Си. Если компилятор языка Си обнаруживает спецификацию класса памяти register в объявлении переменной, а свободного регистра не имеется, или переменная данного типа не может быть размещена в регистре, то переменной выделяется память класса auto. В СП MSC регистровая память всегда выделяется переменным в том порядке, в котором они объявляются в исходном файле. В СП TC, при наличии нескольких переменных класса памяти register в одном объявлении, регистровая память будет выделяться переменным в обратном порядке. Так, по объявлению register i, j; первой получит регистровую память переменная j.
В регистровой памяти может быть размещен объект размером не больше, чем тип int. К переменной, размещенной в регистре, нельзя применять операцию адресации. При вызове функций из блока, в котором определены регистровые переменные, содержимое регистров будет сохранено в памяти, а по возвращении в блок восстановлено.
Для каждого рекурсивного входа в блок порождается новый набор переменных класса памяти auto и register. При этом каждый раз производится инициализация переменных, в объявлении которых заданы инициализаторы.
Переменная, объявленная на внутреннем уровне со спецификацией класса памяти static, имеет глобальное время жизни, но ее область действия распространяется только на блок, в котором она объявлена (и на все вложенные блоки). В отличие от переменных класса памяти auto, переменные, объявленные со спецификацией класса памяти static, сохраняют свое значение при выходе из блока. Переменные класса памяти static могут быть инициализированы константным выражением. Если явной инициализации нет, то переменная класса памяти static автоматически инициализируется нулевым значением. Инициализация выполняется один раз во время компиляции и не повторяется при каждом входе в блок. Все рекурсивные вызовы данного блока будут разделять единственный экземпляр переменной класса памяти static.
Переменная, объявленная со спецификацией класса памяти extern, является ссылкой на переменную с тем же самым именем, определенную на внешнем уровне в любом исходном файле программы. Цель внутреннего объявления extern состоит в том, чтобы сделать определение переменной внешнего уровня (как правило, данное в другом исходном файле) доступным именно внутри данного блока. Внутреннее объявление extern не влияет на область действия объявляемой глобальной переменной в любой другой части программы.
Пример:
inl i = 1; /* определение i */
main()
{
/* объявление i, ссылающееся на данное выше определение */
extern int i;
/* начальное значение а равно нулю; область действия а — функция main */
static int a;
/* b будет (по возможности) помещено в регистр */
register int b = 0;
/* по умолчанию с будет иметь класс памяти auto */
int с = 0;
/* печатаются значения 1, 0, 0, 0*/
printf("%d,%d,%d,%dn", i, a, b, c);
}
other()
/* локальное переопределение переменной i */
int i = 16;
/* область действия переменной а — функция other */
static int a = 2;
a += 2;
/* печатаются значения 16, 4 */
printf("%d,%dn", i, a);
}
Переменная i определяется на внешнем уровне с начальным значением 1; В функции main объявление i является ссылкой на определение переменной i внешнего уровня. Эта ссылка необязательна, поскольку и без нее внешняя переменная i доступна во всех функциях данного исходного файла. Переменная а класса памяти static автоматически инициализируется нулевым значением, так как явная инициализация опущена. Определяется переменная b регистрового класса памяти и переменная с класса памяти auto. Вызывается стандартная функция printf, которая печатает значения 1, 0, 0, 0.
В функции other переменная i переопределяется как локальная переменная с начальным значением 16. Это не влияет на значение внешней переменной i, поскольку эти переменные никак не связаны между собой. Переменная а объявляется со спецификацией класса памяти static и начальным значением 2. Она никак не связана с переменной а, объявленной в функции main, так как область действия переменных класса памяти static на внутреннем уровне ограничена блоком, в котором они объявлены. Значение переменной а увеличивается на 2 и становится равным 4. Если бы функция other была вызвана еще раз в той же функции main, то значение а при входе было бы равно 4, а при выходе—6. Внутренние переменные класса памяти static сохраняют свои значения при входе в блок и выходе из блока, в котором они объявлены. Значение переменной а в функции main при этом не изменилось бы.